[med-svn] [python-cutadapt] 01/08: New upstream version 1.15

Andreas Tille tille at debian.org
Sat Feb 10 12:31:43 UTC 2018


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository python-cutadapt.

commit 500335b7f80fde7af3d35a225b1a37e9f463b980
Author: Andreas Tille <tille at debian.org>
Date:   Sat Feb 10 13:14:30 2018 +0100

    New upstream version 1.15
---
 CHANGES.rst                                        |   35 +-
 MANIFEST.in                                        |   14 +-
 PKG-INFO                                           |    2 +-
 cutadapt.egg-info/entry_points.txt                 |    3 -
 cutadapt.egg-info/requires.txt                     |    1 -
 cutadapt/scripts/__init__.py                       |    0
 doc/conf.py                                        |    6 +-
 doc/develop.rst                                    |  124 +
 doc/guide.rst                                      |  289 +-
 doc/ideas.rst                                      |   16 +-
 doc/index.rst                                      |    1 +
 doc/installation.rst                               |   69 +-
 doc/recipes.rst                                    |   86 +
 setup.cfg                                          |    4 +-
 setup.py                                           |   45 +-
 .../cutadapt.egg-info}/PKG-INFO                    |    2 +-
 .../cutadapt.egg-info}/SOURCES.txt                 |   81 +-
 .../cutadapt.egg-info}/dependency_links.txt        |    0
 src/cutadapt.egg-info/entry_points.txt             |    3 +
 src/cutadapt.egg-info/requires.txt                 |    1 +
 .../cutadapt.egg-info}/top_level.txt               |    0
 {cutadapt => src/cutadapt}/__init__.py             |    0
 .../cutadapt.py => src/cutadapt/__main__.py        |  674 +--
 {cutadapt => src/cutadapt}/_align.c                | 1580 ++---
 {cutadapt => src/cutadapt}/_align.pyx              |    5 +
 {cutadapt => src/cutadapt}/_qualtrim.c             |  266 +-
 {cutadapt => src/cutadapt}/_qualtrim.pyx           |    0
 {cutadapt => src/cutadapt}/_seqio.c                | 6265 ++++++++++++++------
 {cutadapt => src/cutadapt}/_seqio.pyx              |   98 +
 {cutadapt => src/cutadapt}/_version.py             |    4 +-
 {cutadapt => src/cutadapt}/adapters.py             |  165 +-
 {cutadapt => src/cutadapt}/align.py                |    0
 {cutadapt => src/cutadapt}/colorspace.py           |    0
 {cutadapt => src/cutadapt}/compat.py               |    0
 {cutadapt => src/cutadapt}/filters.py              |  183 +-
 {cutadapt => src/cutadapt}/modifiers.py            |   61 +-
 src/cutadapt/pipeline.py                           |  651 ++
 {cutadapt => src/cutadapt}/qualtrim.py             |    0
 {cutadapt => src/cutadapt}/report.py               |  254 +-
 {cutadapt => src/cutadapt}/seqio.py                |  226 +-
 tests/cut/demultiplexed.first.1.fastq              |    4 +
 tests/cut/demultiplexed.first.2.fastq              |    4 +
 tests/cut/demultiplexed.second.1.fastq             |    4 +
 tests/cut/demultiplexed.second.2.fastq             |    4 +
 tests/cut/demultiplexed.unknown.1.fastq            |    8 +
 tests/cut/demultiplexed.unknown.2.fastq            |    8 +
 ...d-not-anchored.fasta => linked-discard-g.fasta} |    6 -
 tests/cut/linked-discard.fasta                     |    6 +
 tests/cut/linked-not-anchored.fasta                |    4 +-
 tests/data/block1.fastq.bz2                        |  Bin 87 -> 0 bytes
 tests/data/block2.fastq.bz2                        |  Bin 206 -> 0 bytes
 tests/{testadapters.py => test_adapters.py}        |   76 +-
 tests/{testalign.py => test_align.py}              |    0
 tests/{testcolorspace.py => test_colorspace.py}    |    4 +-
 tests/{tests.py => test_commandline.py}            |   52 +-
 tests/{testfilters.py => test_filters.py}          |    9 +-
 tests/{testmodifiers.py => test_modifiers.py}      |    0
 tests/{testpaired.py => test_paired.py}            |  257 +-
 tests/{testqualtrim.py => test_qualtrim.py}        |    1 +
 tests/{testseqio.py => test_seqio.py}              |   81 +-
 tests/test_trim.py                                 |   57 +
 tests/testtrim.py                                  |   28 -
 tests/utils.py                                     |   28 +-
 63 files changed, 7696 insertions(+), 4159 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 1ffb8dd..2542e88 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,36 @@
 Changes
 =======
 
+v1.15 (2017-11-23)
+------------------
+
+* Cutadapt can now run on multiple CPU cores in parallel! To enable
+  it, use the option ``-j N`` (or the long form ``--cores=N``), where ``N`` is
+  the number of cores to use. Multi-core support is only available on Python 3,
+  and not yet with some command-line arguments. See
+  :ref:`the new section about multi-core in the documentation <multicore>`
+  for details. When writing ``.gz`` files, make sure you have ``pigz`` installed
+  to get the best speedup.
+* The plan is to make multi-core the default (automatically using as many cores as
+  are available) in future releases, so please test it and `report an
+  issue <https://github.com/marcelm/cutadapt/issues/>`_ if you find problems!
+* `Issue #256 <https://github.com/marcelm/cutadapt/issues/256>`_: ``--discard-untrimmed`` did not
+  have an effect on non-anchored linked adapters.
+* `Issue #118 <https://github.com/marcelm/cutadapt/issues/118>`_:
+  Added support for demultiplexing of paired-end data.
+
+
+v1.14 (2017-06-16)
+------------------
+
+* Fix: Statistics for 3' part of a linked adapter were reported incorrectly
+* Fix `issue #244 <https://github.com/marcelm/cutadapt/issues/244>`_:
+  Quality trimming with ``--nextseq-trim`` would not apply to R2 when
+  trimming paired-end reads.
+* ``--nextseq-trim`` now disables legacy mode.
+* Fix `issue #246 <https://github.com/marcelm/cutadapt/issues/246>`_: installation
+  failed on non-UTF8 locale
+
 v1.13 (2017-03-16)
 ------------------
 
@@ -10,7 +40,8 @@ v1.13 (2017-03-16)
   5' adapter is always anchored in this notation.
 * Issue #224: If you want the 5' part of a linked adapter *not* to be
   anchored, you can now write ``-g ADAPTER...ADAPTER2`` (note ``-g``
-  instead of ``-a``).
+  instead of ``-a``). This feature is experimental and may change behavior
+  in the next release.
 * Issue #236: For more accurate statistics, it is now possible to specify the
   GC content of the input reads with ``--gc-content``. This does
   not change trimming results, only the number in the "expect"
@@ -122,7 +153,7 @@ v1.8 (2015-03-14)
   such as ``-q`` (quality trimming) are applied to *both* reads. For backwards
   compatibility, read modifications are applied to the first read only if
   neither of ``-A``/``-G``/``-B``/``-U`` is used. See `the
-  documentation <http://cutadapt.readthedocs.org/en/latest/guide.html#paired-end>`_
+  documentation <http://cutadapt.readthedocs.io/en/latest/guide.html#paired-end>`_
   for details.
 
   This feature has not been extensively tested, so please give feedback if
diff --git a/MANIFEST.in b/MANIFEST.in
index a758122..87f1708 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,19 +1,13 @@
-# documentation
-include README.rst
 include CHANGES.rst
 include CITATION
 include LICENSE
 include doc/*.rst
 include doc/conf.py
 include doc/Makefile
-include cutadapt/*.pyx
-include cutadapt/_align.c
-include cutadapt/_qualtrim.c
-include cutadapt/_seqio.c
-include bin/_preamble.py
-include tests/test*.py
+include versioneer.py
+include src/cutadapt/*.c
+include src/cutadapt/*.pyx
 include tests/utils.py
+include tests/test_*.py
 graft tests/data
 graft tests/cut
-include versioneer.py
-include cutadapt/_version.py
diff --git a/PKG-INFO b/PKG-INFO
index 41bb393..02fb5e8 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: cutadapt
-Version: 1.13
+Version: 1.15
 Summary: trim adapters from high-throughput sequencing reads
 Home-page: https://cutadapt.readthedocs.io/
 Author: Marcel Martin
diff --git a/cutadapt.egg-info/entry_points.txt b/cutadapt.egg-info/entry_points.txt
deleted file mode 100644
index 3e411b5..0000000
--- a/cutadapt.egg-info/entry_points.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[console_scripts]
-cutadapt = cutadapt.scripts.cutadapt:main
-
diff --git a/cutadapt.egg-info/requires.txt b/cutadapt.egg-info/requires.txt
deleted file mode 100644
index 313bdeb..0000000
--- a/cutadapt.egg-info/requires.txt
+++ /dev/null
@@ -1 +0,0 @@
-xopen>=0.1.1
diff --git a/cutadapt/scripts/__init__.py b/cutadapt/scripts/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/doc/conf.py b/doc/conf.py
index bc03c0c..b2f6ed7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -18,7 +18,7 @@ import os
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath(os.pardir))
+sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, 'src')))
 
 # -- General configuration ------------------------------------------------
 
@@ -66,6 +66,8 @@ if version.endswith('.dirty') and os.environ.get('READTHEDOCS') == 'True':
 # The full version, including alpha/beta/rc tags.
 release = version
 
+suppress_warnings = ['image.nonlocal_uri']
+
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 #language = None
@@ -158,7 +160,7 @@ html_static_path = ['_static']
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-html_use_smartypants = True
+#html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
 #html_sidebars = {}
diff --git a/doc/develop.rst b/doc/develop.rst
new file mode 100644
index 0000000..bd998ff
--- /dev/null
+++ b/doc/develop.rst
@@ -0,0 +1,124 @@
+Developing
+==========
+
+The `Cutadapt source code is on GitHub <https://github.com/marcelm/cutadapt/>`_.
+Cutadapt is written in Python with some extension modules that are written
+in Cython. Cutadapt uses a single code base that is compatible with both
+Python 2 and 3. Python 2.7 is the minimum supported Python version. With
+relatively little effort, compatibility with Python 2.6 could be restored.
+
+
+Development installation
+------------------------
+
+For development, make sure that you install Cython and tox. We also recommend
+using a virtualenv. This sequence of commands should work::
+
+	git clone https://github.com/marcelm/cutadapt.git  # or clone your own fork
+	cd cutadapt
+	virtualenv -p python3 venv  # or omit the "-p python3" for Python 2
+	venv/bin/pip3 install Cython nose tox  # pip3 becomes just pip for Python 2
+	venv/bin/pip3 install -e .
+
+Then you can run Cutadapt like this (or activate the virtualenv and omit the
+``venv/bin`` part)::
+
+	venv/bin/cutadapt --help
+
+The tests can then be run like this::
+
+	venv/bin/nosetests
+
+Or with tox (but then you will need to have binaries for all tested Python
+versions installed)::
+
+    venv/bin/tox
+
+
+Development installation (without virtualenv)
+---------------------------------------------
+
+Alternatively, if you do not want to use virtualenv, you can do the following
+from within the cloned repository::
+
+	python3 setup.py build_ext -i  # omit the "3" for Python 2
+	nosetests
+
+This requires Cython and nose to be installed.
+
+
+Code style
+----------
+
+Cutadapt tries to follow PEP8, with some exceptions:
+
+* Indentation is made with tabs, not with spaces
+* The maximum line length for code 100 characters, not 80, but try to wrap
+  comments at 80 characters for readability.
+
+Yes, there are inconsistencies in the current code base since it’s a few years old already.
+
+
+Making a release
+----------------
+
+If this is the first time you attempt to upload a distribution to PyPI, create a
+configuration file named ``.pypirc`` in your home directory with the following
+contents::
+
+	[distutils]
+	index-servers =
+	    pypi
+
+	[pypi]
+	username=my-user-name
+	password=my-password
+
+See also `this blog post about getting started with
+PyPI <http://peterdowns.com/posts/first-time-with-pypi.html>`_. In particular,
+note that a ``%`` in your password needs to be doubled and that the password
+must *not* be put between quotation marks even if it contains spaces.
+
+Cutadapt uses `versioneer <https://github.com/warner/python-versioneer>`_ to automatically manage
+version numbers. This means that the version is not stored in the source code but derived from
+the most recent Git tag. The following procedure can be used to bump the version and make a new
+release.
+
+#. Update ``CHANGES.rst`` (version number and list of changes)
+
+#. Ensure you have no uncommitted changes in the working copy.
+
+#. Run a ``git pull``.
+
+#. Run ``tox``, ensuring all tests pass.
+
+#. Tag the current commit with the version number (there must be a ``v`` prefix)::
+
+       git tag v0.1
+
+#. Create a distribution (``.tar.gz`` file). Double-check that the auto-generated version number in
+   the tarball is as you expect it by looking at the name of the generated file in ``dist/``::
+
+       python3 setup.py sdist
+
+#. If necessary, pip install ``twine`` and then upload the generated tar file to PyPI::
+
+       twine upload dist/cutadapt-0.1.tar.gz  # adjust version number
+
+#. Push the tag::
+
+       git push --tags
+
+#. Update the `bioconda recipe <https://github.com/bioconda/bioconda-recipes/blob/master/recipes/cutadapt/meta.yaml>`_.
+   It is probly easiest to edit the recipe via the web interface and send in a
+   pull request. Ensure that the list of dependencies (the ``requirements:``
+   section in the recipe) is in sync with the ``setup.py`` file.
+
+   Since this is just a version bump, the pull request does not need a
+   review by other bioconda developers. As soon as the tests pass and if you
+   have the proper permissions, it can be merged directly.
+
+If something went wrong *after* you uploaded a tarball, fix the problem and follow the
+above instructions again, but with an incremented revision in the version number.
+That is, go from version x.y to x.y.1. Do not change a version that has already
+been uploaded.
diff --git a/doc/guide.rst b/doc/guide.rst
index b68640e..e9d1e51 100644
--- a/doc/guide.rst
+++ b/doc/guide.rst
@@ -10,14 +10,19 @@ To trim a 3' adapter, the basic command-line for cutadapt is::
     cutadapt -a AACCGGTT -o output.fastq input.fastq
 
 The sequence of the adapter is given with the ``-a`` option. You need to replace
-``AACCGGTT`` with your actual adapter sequence. Reads are read from the input
-file ``input.fastq`` and written to the output file ``output.fastq``.
+``AACCGGTT`` with the correct adapter sequence. Reads are read from the input
+file ``input.fastq`` and are written to the output file ``output.fastq``.
+
+Compressed in- and output files are also supported::
+
+    cutadapt -a AACCGGTT -o output.fastq.gz input.fastq.gz
 
 Cutadapt searches for the adapter in all reads and removes it when it finds it.
-All reads that were present in the input file will also be present in the output
-file, some of them trimmed, some of them not. Even reads that were trimmed
-entirely (because the adapter was found in the very beginning) are output. All
-of this can be changed with command-line options, explained further down.
+Unless you use a filtering option, all reads that were present in the input file
+will also be present in the output file, some of them trimmed, some of them not.
+Even reads that were trimmed entirely (because the adapter was found in the very
+beginning) are output. All of this can be changed with command-line options,
+explained further down.
 
 
 Input and output file formats
@@ -25,9 +30,9 @@ Input and output file formats
 
 Input files for cutadapt need to be in one the these formats:
 
-* FASTA (file name extensions: ``.fasta``, ``.fa``, ``.fna``)
-* FASTQ (extensions: ``.fastq``, ``.fq``)
-* Any of the above, but compressed as ``.gz`` (even ``.bz2`` and ``.xz`` are supported).
+* FASTA with extensions ``.fasta``, ``.fa`` or ``.fna``
+* FASTQ with extensions ``.fastq`` or ``.fq``
+* Any of the above, but compressed as ``.gz``, ``.bz2`` or ``.xz``
 
 :ref:`Cutadapt’s support for processing of colorspace data is described
 elsewhere <colorspace>`.
@@ -35,20 +40,21 @@ elsewhere <colorspace>`.
 Input and output file formats are recognized from the file name extension. You
 can override the input format with the ``--format`` option.
 
-You can even use this -- without any adapter trimming -- to convert from
-FASTQ to FASTA::
+You can use the automatic format detection to convert from FASTQ to FASTA
+(without doing any adapter trimming)::
 
-    cutadapt -o output.fasta input.fastq.gz
+    cutadapt -o output.fasta.gz input.fastq.gz
 
 
+.. _compressed-files:
+
 Compressed files
 ----------------
 
 Cutadapt supports compressed input and output files. Whether an input file
 needs to be decompressed or an output file needs to be compressed is detected
 automatically by inspecting the file name: If it ends in ``.gz``, then gzip
-compression is assumed. You can therefore run cutadapt like this and it works
-as expected::
+compression is assumed. This is why the example given above works::
 
     cutadapt -a AACCGGTT -o output.fastq.gz input.fastq.gz
 
@@ -58,10 +64,10 @@ Files compressed with bzip2 (``.bz2``) or xz (``.xz``) are also supported, but
 only if the Python installation includes the proper modules. xz files require
 Python 3.3 or later.
 
-Concatenated bz2 files are *not supported* on Python versions before 3.3.
+Concatenated bz2 input files are *not supported* on Python versions before 3.3.
 These files are created by utilities such as ``pbzip2`` (parallel bzip2).
 
-Concatenated gz files *are* supported on all supported Python versions.
+Concatenated gz input files *are* supported on all supported Python versions.
 
 
 Standard input and output
@@ -103,6 +109,53 @@ you could use something like this::
     cutadapt -a AACCGGTT -o /dev/null input.fastq
 
 
+.. _multicore:
+
+Multi-core support
+------------------
+
+Cutadapt supports parallel processing, that is, it can use multiple CPU cores.
+Multi-core is currently not enabled by default. To enable it, use the
+option ``-j N`` (or the spelled-out version ``--cores=N``), where ``N`` is the
+number of cores to use.
+
+Make also sure that you have ``pigz`` (parallel gzip) installed if you use
+multiple cores and write to a ``.gz`` output file. Otherwise, compression of
+the output will be done in a single thread and therefore be the main bottleneck.
+
+.. note::
+    In a future release, the plan is to make cutadapt automatically use as many
+    CPU cores as are available, even when no ``--cores`` option was given.
+    Please help to ensure that multi-core support is as stable as possible by
+    `reporting any problems <https://github.com/marcelm/cutadapt/issues>`_ you
+    may find!
+
+There are some limitations:
+
+* Multi-core is *only* available when you run cutadapt with Python 3.3 or later.
+* Multi-core cutadapt can only write to output files given by ``-o`` and ``-p``.
+  This implies that the following command-line arguments are not compatible with
+  multi-core:
+
+      - ``--info-file``
+      - ``--rest-file``
+      - ``--wildcard-file``
+      - ``--untrimmed-output``, ``--untrimmed-paired-output``
+      - ``--too-short-output``, ``--too-short-paired-output``
+      - ``--too-long-output``, ``--too-long-paired-output``
+      - ``--format``
+      - ``--colorspace``
+
+* Multi-core is also not available when you use cutadapt for demultiplexing.
+
+If you try to use multiple cores with an incompatible commandline option, you
+will get an error message.
+
+Some of these limitations will be lifted in the future, as time allows.
+
+.. versionadded:: 1.15
+
+
 Read processing
 ===============
 
@@ -134,16 +187,17 @@ Removing adapters
 
 Cutadapt supports trimming of multiple types of adapters:
 
-=================================================== ===========================
-Adapter type                                        Command-line option
-=================================================== ===========================
-:ref:`3' adapter <three-prime-adapters>`            ``-a ADAPTER``
-:ref:`5' adapter <five-prime-adapters>`             ``-g ADAPTER``
-:ref:`Anchored 3' adapter <anchored-3adapters>`     ``-a ADAPTER$``
-:ref:`Anchored 5' adapter <anchored-5adapters>`     ``-g ^ADAPTER``
-:ref:`5' or 3' (both possible) <anywhere-adapters>` ``-b ADAPTER``
-:ref:`Linked adapter <linked-adapters>`             ``-a ADAPTER1...ADAPTER2``
-=================================================== ===========================
+======================================================= ===========================
+Adapter type                                            Command-line option
+======================================================= ===========================
+:ref:`3' adapter <three-prime-adapters>`                ``-a ADAPTER``
+:ref:`5' adapter <five-prime-adapters>`                 ``-g ADAPTER``
+:ref:`Anchored 3' adapter <anchored-3adapters>`         ``-a ADAPTER$``
+:ref:`Anchored 5' adapter <anchored-5adapters>`         ``-g ^ADAPTER``
+:ref:`5' or 3' (both possible) <anywhere-adapters>`     ``-b ADAPTER``
+:ref:`Linked adapter <linked-adapters>`                 ``-a ADAPTER1...ADAPTER2``
+:ref:`Non-anchored linked adapter <linked-nonanchored>` ``-g ADAPTER1...ADAPTER2``
+======================================================= ===========================
 
 Here is an illustration of the allowed adapter locations relative to the read
 and depending on the adapter type:
@@ -338,7 +392,7 @@ and you have these input reads::
 
 Trimming with ::
 
-    cutadapt -a FIRST...SECOND input.fastq > output.fastq
+    cutadapt -a FIRST...SECOND -o output.fastq input.fastq
 
 will result in ::
 
@@ -359,18 +413,33 @@ such as ``--info-file``, ``--mask-adapter``.
    Ability to anchor the 3' adapter.
 
 
+.. _linked-nonanchored:
+
 Linked adapters without anchoring
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+This adapter type is especially suited for trimming CRISR screening reads.
+
 Sometimes, the 5' adapter of a linked adapter pair should not be anchored. It is possible to
 specify linked adapters also with ``-g ADAPTER1...ADAPTER2`` (note that ``-g`` is used instead
-of ``-a``). These work just like the linked adapters described in the previous section,
-*except that the 5' adapter is not anchored by default*.
+of ``-a``). These work like the linked adapters described in the previous section, but with
+these two differences:
+
+* The 5' adapter is not anchored by default. (So neither the 5' nor 3' adapter are anchored.)
+* *Both* adapters are required. If one of them is not found, the read is not trimmed.
+
+That is, when you use the `--discard-untrimmed`` option (or ``--trimmed-only``) with a
+linked adapter specified with ``-g``, then a read is considered to be trimmed if *both*
+adapter parts (5' and 3') are present in the read. This is different from linked adapters
+specified with ``-a``, where a non-anchored 3' adapter is optional.
 
 This feature has been added on a tentative basis. It may change in the next program version.
 
 .. versionadded:: 1.13
 
+.. versionchanged:: 1.15
+    Require both adapters for a read to be trimmed.
+
 
 Linked adapter statistics
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -542,17 +611,20 @@ useful for trimming adapters with an embedded variable barcode::
 
     cutadapt -a ACGTAANNNNTTAGC -o output.fastq input.fastq
 
-Wildcard characters in the adapter are enabled by default. Use the option ``-N``
-to disable this.
+Even the ``X`` wildcard that does not match any nucleotide is supported. It is
+useful, for example, :ref:`as a trick for avoiding internal adapter
+matches <avoid-internal-adapter-matches>`.
 
-Matching of wildcards in the reads is also possible, but disabled by default
-in order to avoid matches in reads that consist of many (often low-quality)
-``N`` bases. Use ``--match-read-wildcards`` to enable wildcards also in reads.
+Wildcard characters are by default only allowed in adapter sequences and
+are not recognized when they occur in a read. This is to avoid matches in reads
+that consist of many (often low-quality) ``N`` bases. Use
+``--match-read-wildcards`` to enable wildcards also in reads.
 
-If wildcards are disabled entirely (that is, you use ``-N`` and *do not* use
-``--match-read-wildcards``), then cutadapt compares characters by ASCII value.
-Thus, both the read and adapter can be arbitrary strings (such as ``SEQUENCE``
-or ``ADAPTER`` as used here in the examples).
+Use the option ``-N`` to disable interpretation of wildcard characters even in
+the adapters. If wildcards are disabled entirely, that is, when you use ``-N``
+and *do not* use ``--match-read-wildcards``, then cutadapt compares characters
+by their ASCII value. Thus, both the read and adapter can be arbitrary strings
+(such as ``SEQUENCE`` or ``ADAPTER`` as used here in the examples).
 
 Wildcards do not work in colorspace.
 
@@ -690,9 +762,9 @@ Shortening reads to a fixed length
 To shorten each read down to a certain length, use the ``--length`` option or
 the short version ``-l``::
 
-    cutadapt -l 10 input.fastq > output.fastq
+    cutadapt -l 10 -o output.fastq.gz input.fastq.gz
 
-This shortens all reads from ``input.fastq`` down to 10 bases. The removed bases
+This shortens all reads from ``input.fastq.gz`` down to 10 bases. The removed bases
 are those on the 3' end.
 
 If you want to remove a fixed number of bases from each read, use
@@ -773,18 +845,21 @@ modifications have been applied (adapter removal, quality trimming etc.). A
 processed read can be identical to the input read if no modifications were done.
 
 
-``--minimum-length N`` or ``-m N``
-    Throw away processed reads shorter than *N* bases.
+``--minimum-length LENGTH`` or ``-m LENGTH``
+    Discard processed reads that are shorter than LENGTH. Reads that are too
+    short even before adapter removal are also discarded. Without this option,
+    reads that have a length of zero (empty reads) are kept in the output.
 
 ``--too-short-output FILE``
-    Instead of throwing away the reads that are too short according to ``-m``,
+    Instead of discarding the reads that are too short according to ``-m``,
     write them to *FILE* (in FASTA/FASTQ format).
 
-``--maximum-length N`` or ``-M N``
-    Throw away processed reads longer than *N* bases.
+``--maximum-length LENGTH`` or ``-M LENGTH``
+    Discard processed reads that are longer than LENGTH. Reads that are too
+    long even before adapter removal are also discarded.
 
 ``--too-long-output FILE``
-    Instead of throwing away the reads that are too long (according to ``-M``),
+    Instead of discarding reads that are too long (according to ``-M``),
     write them to *FILE* (in FASTA/FASTQ format).
 
 ``--untrimmed-output FILE``
@@ -792,10 +867,10 @@ processed read can be identical to the input read if no modifications were done.
     of writing them to the regular output file.
 
 ``--discard-trimmed``
-   Throw away reads in which an adapter was found.
+   Discard reads in which an adapter was found.
 
 ``--discard-untrimmed``
-   Throw away reads in which *no* adapter was found. This has the same effect as
+   Discard reads in which *no* adapter was found. This has the same effect as
    specifying ``--untrimmed-output /dev/null``.
 
 The options ``--too-short-output`` and ``--too-long-output`` are applied first.
@@ -884,8 +959,6 @@ The following limitations still exist:
 
 * The ``--info-file``, ``--rest-file`` and ``--wildcard-file`` options write out
   information only from the first read.
-* Demultiplexing is not yet supported with paired-end data.
-
 
 
 .. _filtering-paired:
@@ -894,21 +967,41 @@ Filtering paired-end reads
 --------------------------
 
 The :ref:`filtering options listed above <filtering>` can also be used when
-trimming paired-end data. Since there are two reads, however, the filtering
-criteria are checked for both reads. The question is what to do when a criterion
-applies to only one read and not the other.
-
-By default, the filtering options discard or redirect the read pair if *any*
-of the two reads fulfill the criteria. That is, ``--max-n`` discards the pair
-if one of the two reads has too many ``N`` bases; ``--discard-untrimmed``
-discards the pair if one of the reads does not contain an adapter;
-``--minimum-length`` discards the pair if one of the reads is too short;
-and ``--maximum-length`` discards the pair if one of the reads is too long.
-Note that the ``--discard-trimmed`` filter would also apply because it is also
-the case that at least one of the reads is *trimmed*!
+trimming paired-end data.
+
+Importantly, cutadapt *always discards both reads of a pair* if it determines
+that the pair should be discarded. This ensures that the reads in the output
+files are in sync. (If you don’t want or need this, you can run cutadapt
+separately on the R1 and R2 files.)
+
+The same applies also to the options that redirect reads to other files if they
+fulfill a filtering criterion, such as
+``--too-short-output``/``--too-short-paired-output``. That is, the reads are
+always sent in pairs to these alternative output files.
+
+By default, a read pair is discarded (or redirected) if one of the reads
+(R1 or R2) fulfills the filtering criterion. As an example, if option
+``--minimum-length=20`` is used and paired-end data is processed, a read pair
+if discarded if one of the reads is shorter than 20 nt.
 
 To require that filtering criteria must apply to *both* reads in order for a
-read pair to be considered "filtered", use the option ``--pair-filter=both``.
+read pair to be discarded, use the option ``--pair-filter=both``. The following
+table describes the effect for each filtering option.
+
++----------------------------+------------------------------------------------+-----------------------------------------+
+| Filtering option           | With ``--pair-filter=any``, the pair           | With ``-pair-filter=both``, the pair    |
+|                            | is discarded if ...                            | is discarded if ...                     |
++============================+================================================+=========================================+
+| ``--minimum-length``       | one of the reads is too short                  | both reads are too short                |
++----------------------------+------------------------------------------------+-----------------------------------------+
+| ``--maximum-length``       | one of the reads is too long                   | both reads are too long                 |
++----------------------------+------------------------------------------------+-----------------------------------------+
+| ``--discard-trimmed``      | one of the reads contains an adapter           | both reads contain an adapter           |
++----------------------------+------------------------------------------------+-----------------------------------------+
+| ``--discard-untrimmed``    | one of the reads does not contain an adapter   | both reads do not contain an adapter    |
++----------------------------+------------------------------------------------+-----------------------------------------+
+| ``--max-n``                | one of the reads contains too many ``N`` bases | both reads contain too many ``N`` bases |
++----------------------------+------------------------------------------------+-----------------------------------------+
 
 To further complicate matters, cutadapt switches to a backwards compatibility
 mode ("legacy mode") when none of the uppercase modification options
@@ -927,10 +1020,19 @@ These are the paired-end specific filtering and output options:
     Used together with ``--untrimmed-output``. The second read in a pair is
     written to this file when the processed pair was *not* trimmed.
 
+``--too-short-paired-output FILE``
+    Write the second read in a pair to this file if pair is too short. Use
+    together with ``--too-short-output``.
+
+``--too-long-paired-output FILE``
+    Write the second read in a pair to this file if pair is too long. Use
+    together with ``--too-long-output``.
+
 ``--pair-filter=(any|both)``
     Which of the reads in a paired-end read have to match the filtering
     criterion in order for it to be filtered.
 
+
 Note that the option names can be abbreviated as long as it is clear which
 option is meant (unique prefix). For example, instead of ``--untrimmed-output``
 and ``--untrimmed-paired-output``, you can write ``--untrimmed-o`` and
@@ -1108,10 +1210,6 @@ to the path in which ``{name}`` is replaced with the name of the adapter that
 was found in the read. Reads in which no adapter was found will be written to a
 file in which ``{name}`` is replaced with ``unknown``.
 
-.. note:
-    Demultiplexing is currently only supported for single-end reads. Paired-end
-    support is planned for one of the next versions.
-
 Example::
 
     cutadapt -a one=TATA -a two=GCGC -o trimmed-{name}.fastq.gz input.fastq.gz
@@ -1129,6 +1227,34 @@ abbreviated)::
 
     cutadapt -a file:barcodes.fasta --no-trim --untrimmed-o untrimmed.fastq.gz -o trimmed-{name}.fastq.gz input.fastq.gz
 
+Here is a made-up example for the ``barcodes.fasta`` file::
+
+    >barcode01
+    TTAAGGCC
+    >barcode02
+    TAGCTAGC
+    >barcode03
+    ATGATGAT
+
+Demultiplexing is also supported for paired-end data if you provide the ``{name}`` template
+in both output file names (``-o`` and ``-p``). Paired-end demultiplexing always uses the adapter
+matches of the *first* read to decide where a read should be written.
+If adapters to be found in read 2 are given (``-A``/``-G``), they are detected and removed as normal, but
+these matches do not influence where the read pair is written. This is
+to ensure that read 1 and read 2 are always synchronized. Example::
+
+    cutadapt -a first=AACCGG -a second=TTTTGG -A ACGTACGT -A TGCATGCA -o trimmed-{name}.1.fastq.gz -p trimmed-{name}.2.fastq.gz input.1.fastq.gz input.2.fastq.gz
+
+This will create up to six output files named ``trimmed-first.1.fastq.gz``, ``trimmed-second.1.fastq.gz``,
+``trimmed-unknown.1.fastq.gz`` and ``trimmed-first.2.fastq.gz``, ``trimmed-second.2.fastq.gz``,
+``trimmed-unknown.2.fastq.gz``.
+
+You can use ``--untrimmed-paired-output`` to change the name for the output file that receives the
+untrimmed second reads.
+
+
+.. versionadded:: 1.15
+   Demultiplexing of paired-end data.
 
 .. _more-than-one:
 
@@ -1337,13 +1463,13 @@ The meaning of this should be obvious.
 The next piece of information is this::
 
     No. of allowed errors:
-    0-9 bp: 0; 10-19 bp: 1; 20 bp: 2
+    0-7 bp: 0; 8-15 bp: 1; 16-20 bp: 2
 
 The adapter, as was shown above, has a length of 20
-characters. We are using the default error rate of 0.1. What this
-implies is shown above: Matches up to a length of 9 bp are allowed to
-have no errors. Matches of lengths 10-19 bp are allowd to have 1 error
-and matches of length 20 can have 2 errors. See also :ref:`the section about
+characters. We are using a custom error rate of 0.12. What this
+implies is shown above: Matches up to a length of 7 bp are allowed to
+have no errors. Matches of lengths 8-15 bp are allowd to have 1 error
+and matches of length 16 or more can have 2 errors. See also :ref:`the section about
 error-tolerant matching <error-tolerance>`.
 
 Finally, a table is output that gives more detailed information about
@@ -1356,18 +1482,26 @@ some rows are left out::
     4       57      39.1    0       57
     5       50      9.8     0       50
     6       35      2.4     0       35
+    7       13      0.3     0       1 12
+    8       31      0.1     1       0 31
     ...
     100     397     0.0     3       358 36 3
 
 The first row tells us the following: Three bases were removed in 140
 reads; randomly, one would expect this to occur 156.2 times; the maximum
 number of errors at that match length is 0 (this is actually redundant
-since we know already that no errors are allowed at lengths 0-9 bp).
+since we know already that no errors are allowed at lengths 0-7 bp).
 
 The last column shows the number of reads that had 0, 1, 2 ... errors.
 In the last row, for example, 358 reads matched the adapter with zero
 errors, 36 with 1 error, and 3 matched with 2 errors.
 
+In the row for length 7 is an apparent anomaly, where the max.err column
+is 0 and yet we have 31 reads matching with 1 error. This is because the
+matches are actually contributed by alignments to the first 8 bases of
+the adapter with one deletion, so 7 bases are removed but the error
+cut-off applied is for length 8.
+
 The "expect" column gives only a rough estimate of the number of
 sequences that is expected to match randomly, but it can help to
 estimate whether the matches that were found are true adapter matches
@@ -1410,7 +1544,11 @@ Format of the info file
 When the ``--info-file`` command-line parameter is given, detailed
 information about the found adapters is written to the given file. The
 output is a tab-separated text file. Each line corresponds to one read
-of the input file (unless `--times` is used, see below). The fields are:
+of the input file (unless `--times` is used, see below). A row is written
+for *all* reads, even those that are discarded from the final output
+FASTA/FASTQ due to filtering options (such as ``--minimum-length``).
+
+The fields in each row are:
 
 1. Read name
 2. Number of errors
@@ -1449,7 +1587,8 @@ accordingly for columns 9-11). For subsequent lines, the shown sequence are the
 ones that were used in subsequent rounds of adapter trimming, that is, they get
 successively shorter.
 
-Columns 9-11 have been added in cutadapt version 1.9.
+.. versionadded:: 1.9
+    Columns 9-11 were added.
 
 
 .. _algorithm:
diff --git a/doc/ideas.rst b/doc/ideas.rst
index b5fa9d7..b092c48 100644
--- a/doc/ideas.rst
+++ b/doc/ideas.rst
@@ -15,7 +15,6 @@ improvements.
 - search for adapters in the order in which they are given on the
   command line
 - more tests for the alignment algorithm
-- deprecate ``--rest-file``
 - ``--detect`` prints out best guess which of the given adapters is the correct one
 - alignment algorithm: make a 'banded' version
 - it seems the str.find optimization isn't very helpful. In any case, it should be
@@ -27,6 +26,17 @@ improvements.
 - extensible file type detection
 - the --times setting should be an attribute of Adapter
 
+
+Backwards-incompatible changes
+------------------------------
+
+- Drop ``--rest-file`` support
+- Possibly drop wildcard-file support, extend info-file instead
+- Drop "legacy mode"
+- For non-anchored 5' adapters, find rightmost match
+- Move ``scripts/cutadapt.py`` to ``__main__.py``
+
+
 Specifying adapters
 -------------------
 
@@ -43,7 +53,7 @@ to add new adapter types in the feature.
     anywhere,``-b ADAPTER``, ``-a ...ADAPTER...`` ???
     unconditional,``-u +10``,``-a 10...`` (collides with colorspace)
     unconditional,``-u -10``,``-a ...10$``
-    linked,(not implemented),``-a ADAPTER...ADAPTER`` or ``-a ^ADAPTER...ADAPTER``
+    linked,``-a ADAPTER...ADAPTER``,``-a ADAPTER...ADAPTER`` or ``-a ^ADAPTER...ADAPTER``
 
 Or add only ``-a ADAPTER...`` as an alias for ``-g ^ADAPTER`` and
 ``-a ...ADAPTER`` as an alias for ``-a ADAPTER``.
@@ -99,5 +109,5 @@ Available/used letters for command-line options
 -----------------------------------------------
 
 * Remaining characters: All uppercase letters except A, B, G, M, N, O, U
-* Lowercase letters: i, j, k, l, s, w
+* Lowercase letters: i, j, k, s, w
 * Planned/reserved: Q (paired-end quality trimming), j (multithreading)
diff --git a/doc/index.rst b/doc/index.rst
index f42e58f..d2ec16c 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -12,6 +12,7 @@ Table of contents
    colorspace
    recipes
    ideas
+   develop
    changes
 
 
diff --git a/doc/installation.rst b/doc/installation.rst
index 93ab554..164ec23 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -2,8 +2,12 @@
 Installation
 ============
 
-Quickstart
-----------
+Cutadapt is being developed and tested under Linux. Users have run it
+successfully under macOS and Windows.
+
+
+Quick installation
+------------------
 
 The easiest way to install cutadapt is to use ``pip`` on the command line::
 
@@ -24,7 +28,7 @@ If you want to avoid typing the full path, add the directory
 Installation with conda
 -----------------------
 
-Alternatively, cutadapt is also available as a conda package from the
+Alternatively, cutadapt is available as a conda package from the
 `bioconda channel <https://bioconda.github.io/>`_. If you do not have conda,
 `install miniconda <http://conda.pydata.org/miniconda.html>`_ first.
 Then install cutadapt like this::
@@ -34,35 +38,30 @@ Then install cutadapt like this::
 If neither `pip` nor `conda` installation works, keep reading.
 
 
+.. _dependencies:
+
 Dependencies
 ------------
 
-Cutadapt requires this software to be installed:
+Cutadapt installation requires this software to be installed:
 
-* One of Python 2.7, 3.3, 3.4 or 3.5.
-* A C compiler.
+* Python 2.7 or at least Python 3.3
+* Possibly a C compiler. For Linux, cutadapt packages are provided as
+  so-called “wheels” (``.whl`` files) which come pre-compiled.
 
 Under Ubuntu, you may need to install the packages ``build-essential`` and
-``python-dev`` (or ``python3-dev``).
+``python-dev`` (or ``python3-dev``) to get a C compiler.
 
-
-Installation
-------------
-
-If you have already downloaded and unpacked the ``.tar.gz`` file, then
-installation is done like this (replace "python" with "python3" to
-install the Python 3 version)::
-
-    python setup.py install --user
+On Windows, you need `Microsoft Visual C++ Compiler for
+Python 2.7 <https://www.microsoft.com/en-us/download/details.aspx?id=44266>`_.
 
 If you get an error message::
 
     error: command 'gcc' failed with exit status 1
 
-Then check the entire error message. If it says something about a missing ``Python.h``
-file, then you need to install the Python development packages. The
-appropriate package is called ``python-dev`` in Ubuntu (or ``python3-dev``
-for Python 3).
+Then check the entire error message. If it says something about a missing
+``Python.h`` file, then the problem are missing Python development
+packages (``python-dev``/``python3-dev`` in Ubuntu).
 
 
 System-wide installation (root required)
@@ -124,3 +123,33 @@ like this::
 Please note that there is no need to “activate” the virtual environment:
 Activation merely adds the ``bin/`` directory to the ``$PATH``, so the
 ``prepend_path`` directive is equivalent to activating the virtual environment.
+
+
+Installing the development version
+----------------------------------
+
+We recommend that you install cutadapt into a so-called virtual environment if
+you decide to use the development version. The virtual environment is a single
+directory that contains everything needed to run the software. Nothing else on
+your system is changed, so you can simply uninstall this particular version of
+cutadapt by removing the directory with the virtual environment.
+
+The following instructions work on Linux using Python 3. Make sure you have
+installed the :ref:`dependencies <dependencies>` (``python3-dev`` and
+``build-essential`` on Ubuntu)!
+
+First, choose where you want to place the directory with the virtual
+environment and what you want to call it. Let us assume you chose the path
+``~/cutadapt-venv``. Then use these commands for the installation::
+
+    python3 -m venv ~/cutadapt-venv
+    ~/cutadapt-venv/bin/pip install Cython
+    ~/cutadapt-venv/bin/pip install https://github.com/marcelm/cutadapt/archive/master.zip
+
+To run cutadapt and see the version number, type ::
+
+    ~/cutadapt-venv/bin/cutadapt --version
+
+The reported version number will be something like ``1.14+65.g5610275``. This
+means that you are now running a cutadapt version that contains 65 additional
+changes (*commits*) since version 1.14.
diff --git a/doc/recipes.rst b/doc/recipes.rst
index 7a85ea0..b6d225a 100644
--- a/doc/recipes.rst
+++ b/doc/recipes.rst
@@ -5,6 +5,7 @@ Recipes (FAQ)
 This section gives answers to frequently asked questions. It shows you how to
 get cutadapt to do what you want it to do!
 
+.. _avoid-internal-adapter-matches:
 
 Avoid internal adapter matches
 ------------------------------
@@ -100,6 +101,91 @@ would match the end of every read (because ``N`` matches anything), and ten
 bases would then be removed from every read.
 
 
+Trimming (amplicon-) primers from both ends of paired-end reads
+---------------------------------------------------------------
+
+If you want to remove primer sequences that flank your sequence of
+interest, you should use a :ref:`"linked adapter" <linked-adapters>`
+to remove them. If you have paired-end data (with R1 and R2), you
+can correctly trim both R1 and R2 by using linked adapters for both
+R1 and R2. Here is how to do this.
+
+The full DNA fragment that is put on the sequencer looks like this
+(looking only at the forward strand):
+
+   5' sequencing primer -- forward primer -- sequence of interest -- reverse complement of reverse primer -- reverse complement of 3' sequencing primer
+
+Since sequencing of R1 starts after the 5' sequencing primer, R1 will
+start with the forward primer and then continue into the sequence of
+interest and into the two primers to the right of it, depending on
+the read length and how long the sequence of interest is. For R1,
+the linked adapter option that needs to be used is therefore ::
+
+    -a FWDPRIMER...RCREVPRIMER
+
+where ``FWDPRIMER`` needs to be replaced with the sequence of your
+forward primer and ``RCREVPRIMER`` with the reverse complement of
+the reverse primer. The three dots ``...`` need to be entered
+as they are -- they tell cutadapt that this is a linked adapter
+with a 5' and a 3' part.
+
+Sequencing of R2 starts before the 3' sequencing primer and
+proceeds along the reverse-complementary strand. For the correct
+linked adapter, the sequences from above therefore need to be
+swapped and reverse-complemented::
+
+    -A REVPRIMER...RCFWDPRIMER
+
+The uppercase ``-A`` specifies that this option is
+meant to work on R2. Similar to above, ``REVPRIMER`` is
+the sequence of the reverse primer and ``RCFWDPRIMER`` is the
+reverse-complement of the forward primer. Note that cutadapt
+does not reverse-complement any sequences of its own; you
+will have to do that yourself.
+
+Finally, you may want to filter the trimmed read pairs.
+Use ``--discard-untrimmed`` to throw away all read pairs in
+which R1 doesn’t start with ``FWDPRIMER`` or in which R2
+does not start with ``REVPRIMER``.
+
+A note on how the filtering works: In linked adapters, by default
+the first part (before the ``...``) is anchored. Anchored
+sequences *must* occur. If they don’t, then the other sequence
+(after the ``...``) is not even searched for and the entire
+read is internally marked as “untrimmed”. This is done for both
+R1 and R2 and as soon as *any* of them is marked as “untrimmed”,
+the entire pair is considered to be “untrimmed”. If
+``--discard-untrimmed`` is used, this means that the entire
+pair is discarded if R1 or R2 are untrimmed. (Option
+``--pair-filter=both`` can be used to change this to require
+that *both* were marked as untrimmed.)
+
+In summary, this is how to trim your data and discard all
+read pairs that do not contain the primer sequences that
+you know must be there::
+
+    cutadapt -a FWDPRIMER...RCREVPRIMER -A REVPRIMER...RCFWDPRIMER --discard-untrimmed -o out.1.fastq.gz -p out.2.fastq.gz in.1.fastq.gz in.2.fastq.gz
+
+
+Piping paired-end data
+----------------------
+
+Sometimes it is necessary to run cutadapt twice on your data. For example, when
+you want to change the order in which read modification or filtering options are
+applied. To simplify this, you can use Unix pipes (``|``), but this is more
+difficult with paired-end data since then input and output consists of two files
+each.
+
+The solution is to interleave the paired-end data, send it over the pipe
+and then de-interleave it in the other process. Here is how this looks in
+principle::
+
+    cutadapt [options] --interleaved in.1.fastq.gz in.2.fastq.gz | \
+      cutadapt [options] --interleaved -o out.1.fastq.gz -p out.2.fastq.gz -
+
+Note the ``-`` character in the second invocation to cutadapt.
+
+
 Other things (unfinished)
 -------------------------
 
diff --git a/setup.cfg b/setup.cfg
index 08fbaca..cc8fc93 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,13 +1,13 @@
 [versioneer]
 vcs = git
 style = pep440
-versionfile_source = cutadapt/_version.py
+versionfile_source = src/cutadapt/_version.py
 versionfile_build = cutadapt/_version.py
 tag_prefix = v
 parentdir_prefix = cutadapt-
 
 [egg_info]
 tag_build = 
-tag_date = 0
 tag_svn_revision = 0
+tag_date = 0
 
diff --git a/setup.py b/setup.py
index f3dc0a0..ba55700 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ Build cutadapt.
 import sys
 import os.path
 
-from setuptools import setup, Extension
+from setuptools import setup, Extension, find_packages
 from distutils.version import LooseVersion
 from distutils.command.sdist import sdist as _sdist
 from distutils.command.build_ext import build_ext as _build_ext
@@ -12,8 +12,8 @@ import versioneer
 
 MIN_CYTHON_VERSION = '0.24'
 
-if sys.version_info < (2, 6):
-	sys.stdout.write("At least Python 2.6 is required.\n")
+if sys.version_info < (2, 7):
+	sys.stdout.write("At least Python 2.7 is required.\n")
 	sys.exit(1)
 
 
@@ -54,9 +54,9 @@ def check_cython_version():
 
 
 extensions = [
-	Extension('cutadapt._align', sources=['cutadapt/_align.pyx']),
-	Extension('cutadapt._qualtrim', sources=['cutadapt/_qualtrim.pyx']),
-	Extension('cutadapt._seqio', sources=['cutadapt/_seqio.pyx']),
+	Extension('cutadapt._align', sources=['src/cutadapt/_align.pyx']),
+	Extension('cutadapt._qualtrim', sources=['src/cutadapt/_qualtrim.pyx']),
+	Extension('cutadapt._seqio', sources=['src/cutadapt/_seqio.pyx']),
 ]
 
 cmdclass = versioneer.get_cmdclass()
@@ -92,24 +92,27 @@ class sdist(versioneer_sdist):
 cmdclass['build_ext'] = build_ext
 cmdclass['sdist'] = sdist
 
-with open('README.rst') as f:
+
+encoding_arg = {'encoding': 'utf-8'} if sys.version > '3' else dict()
+with open('README.rst', **encoding_arg) as f:
 	long_description = f.read()
 
 setup(
-	name = 'cutadapt',
-	version = versioneer.get_version(),
-	author = 'Marcel Martin',
-	author_email = 'marcel.martin at scilifelab.se',
-	url = 'https://cutadapt.readthedocs.io/',
-	description = 'trim adapters from high-throughput sequencing reads',
-	long_description = long_description,
-	license = 'MIT',
-	cmdclass = cmdclass,
-	ext_modules = extensions,
-	packages = ['cutadapt', 'cutadapt.scripts'],
-	install_requires = ['xopen>=0.1.1'],
-	entry_points = {'console_scripts': ['cutadapt = cutadapt.scripts.cutadapt:main']},
-	classifiers = [
+	name='cutadapt',
+	version=versioneer.get_version(),
+	author='Marcel Martin',
+	author_email='marcel.martin at scilifelab.se',
+	url='https://cutadapt.readthedocs.io/',
+	description='trim adapters from high-throughput sequencing reads',
+	long_description=long_description,
+	license='MIT',
+	cmdclass=cmdclass,
+	ext_modules=extensions,
+	package_dir={'': 'src'},
+	packages=find_packages('src'),
+	install_requires=['xopen>=0.3.2'],
+	entry_points={'console_scripts': ['cutadapt = cutadapt.__main__:main']},
+	classifiers=[
 		"Development Status :: 5 - Production/Stable",
 		"Environment :: Console",
 		"Intended Audience :: Science/Research",
diff --git a/cutadapt.egg-info/PKG-INFO b/src/cutadapt.egg-info/PKG-INFO
similarity index 99%
rename from cutadapt.egg-info/PKG-INFO
rename to src/cutadapt.egg-info/PKG-INFO
index 41bb393..02fb5e8 100644
--- a/cutadapt.egg-info/PKG-INFO
+++ b/src/cutadapt.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: cutadapt
-Version: 1.13
+Version: 1.15
 Summary: trim adapters from high-throughput sequencing reads
 Home-page: https://cutadapt.readthedocs.io/
 Author: Marcel Martin
diff --git a/cutadapt.egg-info/SOURCES.txt b/src/cutadapt.egg-info/SOURCES.txt
similarity index 78%
rename from cutadapt.egg-info/SOURCES.txt
rename to src/cutadapt.egg-info/SOURCES.txt
index 6bf83fc..d24103f 100644
--- a/cutadapt.egg-info/SOURCES.txt
+++ b/src/cutadapt.egg-info/SOURCES.txt
@@ -6,50 +6,51 @@ README.rst
 setup.cfg
 setup.py
 versioneer.py
-cutadapt/__init__.py
-cutadapt/_align.c
-cutadapt/_align.pyx
-cutadapt/_qualtrim.c
-cutadapt/_qualtrim.pyx
-cutadapt/_seqio.c
-cutadapt/_seqio.pyx
-cutadapt/_version.py
-cutadapt/adapters.py
-cutadapt/align.py
-cutadapt/colorspace.py
-cutadapt/compat.py
-cutadapt/filters.py
-cutadapt/modifiers.py
-cutadapt/qualtrim.py
-cutadapt/report.py
-cutadapt/seqio.py
-cutadapt.egg-info/PKG-INFO
-cutadapt.egg-info/SOURCES.txt
-cutadapt.egg-info/dependency_links.txt
-cutadapt.egg-info/entry_points.txt
-cutadapt.egg-info/requires.txt
-cutadapt.egg-info/top_level.txt
-cutadapt/scripts/__init__.py
-cutadapt/scripts/cutadapt.py
 doc/Makefile
 doc/changes.rst
 doc/colorspace.rst
 doc/conf.py
+doc/develop.rst
 doc/guide.rst
 doc/ideas.rst
 doc/index.rst
 doc/installation.rst
 doc/recipes.rst
-tests/testadapters.py
-tests/testalign.py
-tests/testcolorspace.py
-tests/testfilters.py
-tests/testmodifiers.py
-tests/testpaired.py
-tests/testqualtrim.py
-tests/tests.py
-tests/testseqio.py
-tests/testtrim.py
+src/cutadapt/__init__.py
+src/cutadapt/__main__.py
+src/cutadapt/_align.c
+src/cutadapt/_align.pyx
+src/cutadapt/_qualtrim.c
+src/cutadapt/_qualtrim.pyx
+src/cutadapt/_seqio.c
+src/cutadapt/_seqio.pyx
+src/cutadapt/_version.py
+src/cutadapt/adapters.py
+src/cutadapt/align.py
+src/cutadapt/colorspace.py
+src/cutadapt/compat.py
+src/cutadapt/filters.py
+src/cutadapt/modifiers.py
+src/cutadapt/pipeline.py
+src/cutadapt/qualtrim.py
+src/cutadapt/report.py
+src/cutadapt/seqio.py
+src/cutadapt.egg-info/PKG-INFO
+src/cutadapt.egg-info/SOURCES.txt
+src/cutadapt.egg-info/dependency_links.txt
+src/cutadapt.egg-info/entry_points.txt
+src/cutadapt.egg-info/requires.txt
+src/cutadapt.egg-info/top_level.txt
+tests/test_adapters.py
+tests/test_align.py
+tests/test_colorspace.py
+tests/test_commandline.py
+tests/test_filters.py
+tests/test_modifiers.py
+tests/test_paired.py
+tests/test_qualtrim.py
+tests/test_seqio.py
+tests/test_trim.py
 tests/utils.py
 tests/cut/454.fa
 tests/cut/SRR2040271_1.fastq
@@ -58,6 +59,12 @@ tests/cut/anchored.fasta
 tests/cut/anchored_no_indels.fasta
 tests/cut/anchored_no_indels_wildcard.fasta
 tests/cut/anywhere_repeat.fastq
+tests/cut/demultiplexed.first.1.fastq
+tests/cut/demultiplexed.first.2.fastq
+tests/cut/demultiplexed.second.1.fastq
+tests/cut/demultiplexed.second.2.fastq
+tests/cut/demultiplexed.unknown.1.fastq
+tests/cut/demultiplexed.unknown.2.fastq
 tests/cut/discard-untrimmed.fastq
 tests/cut/discard.fastq
 tests/cut/dos.fastq
@@ -72,6 +79,8 @@ tests/cut/illumina64.fastq
 tests/cut/interleaved.fastq
 tests/cut/issue46.fasta
 tests/cut/linked-anchored.fasta
+tests/cut/linked-discard-g.fasta
+tests/cut/linked-discard.fasta
 tests/cut/linked-not-anchored.fasta
 tests/cut/linked.fasta
 tests/cut/lowercase.fastq
@@ -159,8 +168,6 @@ tests/data/anchored-back.fasta
 tests/data/anchored.fasta
 tests/data/anchored_no_indels.fasta
 tests/data/anywhere_repeat.fastq
-tests/data/block1.fastq.bz2
-tests/data/block2.fastq.bz2
 tests/data/dos.fastq
 tests/data/empty.fastq
 tests/data/example.fa
diff --git a/cutadapt.egg-info/dependency_links.txt b/src/cutadapt.egg-info/dependency_links.txt
similarity index 100%
rename from cutadapt.egg-info/dependency_links.txt
rename to src/cutadapt.egg-info/dependency_links.txt
diff --git a/src/cutadapt.egg-info/entry_points.txt b/src/cutadapt.egg-info/entry_points.txt
new file mode 100644
index 0000000..f43dab1
--- /dev/null
+++ b/src/cutadapt.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+cutadapt = cutadapt.__main__:main
+
diff --git a/src/cutadapt.egg-info/requires.txt b/src/cutadapt.egg-info/requires.txt
new file mode 100644
index 0000000..9176067
--- /dev/null
+++ b/src/cutadapt.egg-info/requires.txt
@@ -0,0 +1 @@
+xopen>=0.3.2
diff --git a/cutadapt.egg-info/top_level.txt b/src/cutadapt.egg-info/top_level.txt
similarity index 100%
rename from cutadapt.egg-info/top_level.txt
rename to src/cutadapt.egg-info/top_level.txt
diff --git a/cutadapt/__init__.py b/src/cutadapt/__init__.py
similarity index 100%
rename from cutadapt/__init__.py
rename to src/cutadapt/__init__.py
diff --git a/cutadapt/scripts/cutadapt.py b/src/cutadapt/__main__.py
similarity index 63%
rename from cutadapt/scripts/cutadapt.py
rename to src/cutadapt/__main__.py
index 87a7b77..c428c82 100755
--- a/cutadapt/scripts/cutadapt.py
+++ b/src/cutadapt/__main__.py
@@ -62,10 +62,9 @@ from cutadapt import check_importability
 check_importability()
 
 import sys
-import time
 import errno
+import time
 from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
-import functools
 import logging
 import platform
 import textwrap
@@ -76,11 +75,9 @@ from cutadapt.adapters import AdapterParser
 from cutadapt.modifiers import (LengthTagModifier, SuffixRemover, PrefixSuffixAdder,
 	DoubleEncoder, ZeroCapper, PrimerTrimmer, QualityTrimmer, UnconditionalCutter,
 	NEndTrimmer, AdapterCutter, NextseqQualityTrimmer, Shortener)
-from cutadapt.filters import (NoFilter, PairedNoFilter, Redirector, PairedRedirector,
-	LegacyPairedRedirector, TooShortReadFilter, TooLongReadFilter,
-	Demultiplexer, NContentFilter, DiscardUntrimmedFilter, DiscardTrimmedFilter)
 from cutadapt.report import Statistics, print_report, redirect_standard_output
-
+from cutadapt.pipeline import SingleEndPipeline, PairedEndPipeline, OutputFiles, ParallelPipelineRunner, available_cpu_count
+from cutadapt.compat import PY3
 
 logger = logging.getLogger()
 
@@ -94,100 +91,17 @@ class CommandlineError(Exception):
 	pass
 
 
-class RestFileWriter(object):
-	def __init__(self, file):
-		self.file = file
-
-	def write(self, match):
-		rest = match.rest()
-		if len(rest) > 0:
-			print(rest, match.read.name, file=self.file)
-
-
-class Pipeline(object):
-	"""
-	Processing pipeline that loops over reads and applies modifiers and filters
-	"""
-	def __init__(self):
-		self._close_files = []
-
-	def register_file_to_close(self, file):
-		if file is not None and file is not sys.stdin and file is not sys.stdout:
-			self._close_files.append(file)
-
-	def close_files(self):
-		for f in self._close_files:
-			f.close()
-
-	def process_reads(self):
-		raise NotImplementedError()
-
-	def run(self):
-		start_time = time.clock()
-		(n, total1_bp, total2_bp) = self.process_reads()
-		self.close_files()
-		elapsed_time = time.clock() - start_time
-		# TODO
-		m = self.modifiers if hasattr(self, 'modifiers') else self.modifiers1
-		m2 = getattr(self, 'modifiers2', [])
-		stats = Statistics()
-		stats.collect(n, total1_bp, total2_bp, elapsed_time, m, m2, self.filters)
-		return stats
-
-
-class SingleEndPipeline(Pipeline):
-	"""
-	Processing pipeline for single-end reads
-	"""
-	def __init__(self, reader, modifiers, filters):
-		super(SingleEndPipeline, self).__init__()
-		self.reader = reader
-		self.modifiers = modifiers
-		self.filters = filters
-
-	def process_reads(self):
-		"""Run the pipeline. Return statistics"""
-		n = 0  # no. of processed reads  # TODO turn into attribute
-		total_bp = 0
-		for read in self.reader:
-			n += 1
-			total_bp += len(read.sequence)
-			for modifier in self.modifiers:
-				read = modifier(read)
-			for filter in self.filters:
-				if filter(read):
-					break
-		return (n, total_bp, None)
-
-
-class PairedEndPipeline(Pipeline):
+class NiceFormatter(logging.Formatter):
 	"""
-	Processing pipeline for paired-end reads.
+	Do not prefix "INFO:" to info-level log messages (but do it for all other
+	levels).
+
+	Based on http://stackoverflow.com/a/9218261/715090 .
 	"""
-	def __init__(self, paired_reader, modifiers1, modifiers2, filters):
-		super(PairedEndPipeline, self).__init__()
-		self.paired_reader = paired_reader
-		self.modifiers1 = modifiers1
-		self.modifiers2 = modifiers2
-		self.filters = filters
-
-	def process_reads(self):
-		n = 0  # no. of processed reads
-		total1_bp = 0
-		total2_bp = 0
-		for read1, read2 in self.paired_reader:
-			n += 1
-			total1_bp += len(read1.sequence)
-			total2_bp += len(read2.sequence)
-			for modifier in self.modifiers1:
-				read1 = modifier(read1)
-			for modifier in self.modifiers2:
-				read2 = modifier(read2)
-			for filter in self.filters:
-				# Stop writing as soon as one of the filters was successful.
-				if filter(read1, read2):
-					break
-		return (n, total1_bp, total2_bp)
+	def format(self, record):
+		if record.levelno != logging.INFO:
+			record.msg = '{}: {}'.format(record.levelname, record.msg)
+		return super(NiceFormatter, self).format(record)
 
 
 def setup_logging(stdout=False, quiet=False):
@@ -197,7 +111,7 @@ def setup_logging(stdout=False, quiet=False):
 	# Due to backwards compatibility, logging output is sent to standard output
 	# instead of standard error if the -o option is used.
 	stream_handler = logging.StreamHandler(sys.stdout if stdout else sys.stderr)
-	stream_handler.setFormatter(logging.Formatter('%(message)s'))
+	stream_handler.setFormatter(NiceFormatter())
 	stream_handler.setLevel(logging.ERROR if quiet else logging.INFO)
 	logger.setLevel(logging.INFO)
 	logger.addHandler(stream_handler)
@@ -212,12 +126,16 @@ def get_option_parser():
 		help="Input file format; can be either 'fasta', 'fastq' or 'sra-fastq'. "
 			"Ignored when reading csfasta/qual files. Default: auto-detect "
 			"from file name extension.")
+	parser.add_option('-j', '--cores', type=int, default=1,
+		help='Number of CPU cores to use. Default: %default')
 
-	# Hidden option for now
+	# Hidden options
 	parser.add_option("--gc-content", type=float, default=50,  # it's a percentage
 		help=SUPPRESS_HELP)
+	parser.add_option("--buffer-size", type=int, default=4000000,
+		help=SUPPRESS_HELP)  # buffer size for the reader process when running in parallel
 
-	group = OptionGroup(parser, "Finding adapters:",
+	group = OptionGroup(parser, "Finding adapters",
 		description="Parameters -a, -g, -b specify adapters to be removed from "
 			"each read (or from the first read in a pair if data is paired). "
 			"If specified multiple times, only the best matching adapter is "
@@ -253,9 +171,8 @@ def get_option_parser():
 	group.add_option("-n", "--times", type=int, metavar="COUNT", default=1,
 		help="Remove up to COUNT adapters from each read. Default: %default")
 	group.add_option("-O", "--overlap", type=int, metavar="MINLENGTH", default=3,
-		help="If the overlap between the read and the adapter is shorter than "
-			"MINLENGTH, the read is not modified. Reduces the no. of bases "
-			"trimmed due to random adapter matches. Default: %default")
+		help="Require MINLENGTH overlap between read and adapter for an adapter "
+			"to be found. Default: %default")
 	group.add_option("--match-read-wildcards", action="store_true", default=False,
 		help="Interpret IUPAC wildcards in reads. Default: %default")
 	group.add_option("-N", "--no-match-adapter-wildcards", action="store_false",
@@ -273,10 +190,11 @@ def get_option_parser():
 		help="Remove bases from each read (first read only if paired). "
 			"If LENGTH is positive, remove bases from the beginning. "
 			"If LENGTH is negative, remove bases from the end. "
-			"Can be used twice if LENGTHs have different signs.")
+			"Can be used twice if LENGTHs have different signs. "
+			"This is applied *before* adapter trimming.")
 	group.add_option("--nextseq-trim", type=int, default=None, metavar="3'CUTOFF",
 		help="NextSeq-specific quality trimming (each read). Trims also dark "
-			"cycles appearing as high-quality G bases (EXPERIMENTAL).")
+			"cycles appearing as high-quality G bases.")
 	group.add_option("-q", "--quality-cutoff", default=None, metavar="[5'CUTOFF,]3'CUTOFF",
 		help="Trim low-quality bases from 5' and/or 3' ends of each read before "
 			"adapter removal. Applied to both reads if data is paired. If one "
@@ -288,7 +206,7 @@ def get_option_parser():
 			"+ QUALITY_BASE). This needs to be set to 64 for some old Illumina "
 			"FASTQ files. Default: %default")
 	group.add_option("--length", "-l", type=int, default=None, metavar="LENGTH",
-			help="Shorten reads to LENGTH. This and the following modifications"
+			help="Shorten reads to LENGTH. This and the following modifications "
 			"are applied after adapter trimming.")
 	group.add_option("--trim-n", action='store_true', default=False,
 		help="Trim N's on ends of reads.")
@@ -305,25 +223,22 @@ def get_option_parser():
 		help="Add this suffix to read names; can also include {name}")
 	parser.add_option_group(group)
 
-	group = OptionGroup(parser, "Filtering of processed reads")
+	group = OptionGroup(parser, "Filtering of processed reads",
+		description="Filters are applied after above read modifications. "
+			"Paired-end reads are always discarded pairwise (see also "
+			"--pair-filter).")
 	group.add_option("-m", "--minimum-length", type=int, default=0, metavar="LENGTH",
-		help="Discard trimmed reads that are shorter than LENGTH. Reads that "
-			"are too short even before adapter removal are also discarded. In "
-			"colorspace, an initial primer is not counted. Default: 0")
+		help="Discard reads shorter than LENGTH. Default: 0")
 	group.add_option("-M", "--maximum-length", type=int, default=sys.maxsize, metavar="LENGTH",
-		help="Discard trimmed reads that are longer than LENGTH. "
-			"Reads that are too long even before adapter removal "
-			"are also discarded. In colorspace, an initial primer "
-			"is not counted. Default: no limit")
+		help="Discard reads longer than LENGTH. Default: no limit")
 	group.add_option("--max-n", type=float, default=-1.0, metavar="COUNT",
-		help="Discard reads with too many N bases. If COUNT is an integer, it "
-			"is treated as the absolute number of N bases. If it is between 0 "
-			"and 1, it is treated as the proportion of N's allowed in a read.")
+		help="Discard reads with more than COUNT 'N' bases. If COUNT is a number "
+			"between 0 and 1, it is interpreted as a fraction of the read length.")
 	group.add_option("--discard-trimmed", "--discard", action='store_true', default=False,
 		help="Discard reads that contain an adapter. Also use -O to avoid "
 			"discarding too many randomly matching reads!")
 	group.add_option("--discard-untrimmed", "--trimmed-only", action='store_true', default=False,
-		help="Discard reads that do not contain the adapter.")
+		help="Discard reads that do not contain an adapter.")
 	parser.add_option_group(group)
 
 	group = OptionGroup(parser, "Output")
@@ -339,11 +254,10 @@ def get_option_parser():
 			"See the documentation for the file format.")
 	group.add_option("-r", "--rest-file", metavar="FILE",
 		help="When the adapter matches in the middle of a read, write the "
-			"rest (after the adapter) into FILE.")
+			"rest (after the adapter) to FILE.")
 	group.add_option("--wildcard-file", metavar="FILE",
-		help="When the adapter has N bases (wildcards), write adapter bases "
-			"matching wildcard positions to FILE. When there are indels in the "
-			"alignment, this will often not be accurate.")
+		help="When the adapter has N wildcard bases, write adapter bases "
+			"matching wildcard positions to FILE. (Inaccurate with indels.)")
 	group.add_option("--too-short-output", metavar="FILE",
 		help="Write reads that are too short (according to length specified by "
 		"-m) to FILE. Default: discard reads")
@@ -357,25 +271,22 @@ def get_option_parser():
 
 	group = OptionGroup(parser, "Colorspace options")
 	group.add_option("-c", "--colorspace", action='store_true', default=False,
-		help="Enable colorspace mode: Also trim the color that is adjacent to the found adapter.")
+		help="Enable colorspace mode")
 	group.add_option("-d", "--double-encode", action='store_true', default=False,
 		help="Double-encode colors (map 0,1,2,3,4 to A,C,G,T,N).")
 	group.add_option("-t", "--trim-primer", action='store_true', default=False,
-		help="Trim primer base and the first color (which is the transition "
-			"to the first nucleotide)")
+		help="Trim primer base and the first color")
 	group.add_option("--strip-f3", action='store_true', default=False,
 		help="Strip the _F3 suffix of read names")
 	group.add_option("--maq", "--bwa", action='store_true', default=False,
 		help="MAQ- and BWA-compatible colorspace output. This enables -c, -d, "
 			"-t, --strip-f3 and -y '/1'.")
-	group.add_option("--no-zero-cap", dest='zero_cap', action='store_false',
-		help="Do not change negative quality values to zero in colorspace "
-			"data. By default, they are since many tools have problems with "
-			"negative qualities.")
 	group.add_option("--zero-cap", "-z", action='store_true',
-		help="Change negative quality values to zero. This is enabled "
-		"by default when -c/--colorspace is also enabled. Use the above option "
-		"to disable it.")
+		help="Change negative quality values to zero. Enabled by default "
+			"in colorspace mode since many tools have problems with "
+			"negative qualities")
+	group.add_option("--no-zero-cap", dest='zero_cap', action='store_false',
+		help="Disable zero capping")
 	parser.set_defaults(zero_cap=None, action='trim')
 	parser.add_option_group(group)
 
@@ -397,7 +308,7 @@ def get_option_parser():
 	group.add_option("--pair-filter", metavar='(any|both)', default=None,
 		choices=("any", "both"),
 		help="Which of the reads in a paired-end read have to match the "
-			"filtering criterion in order for it to be filtered. "
+			"filtering criterion in order for the pair to be filtered. "
 			"Default: any")
 	group.add_option("--interleaved", action='store_true', default=False,
 		help="Read and write interleaved paired-end reads.")
@@ -417,58 +328,191 @@ def get_option_parser():
 	return parser
 
 
-def pipeline_from_parsed_args(options, args, default_outfile):
+def parse_cutoffs(s):
+	"""Parse a string INT[,INT] into a two-element list of integers"""
+	cutoffs = s.split(',')
+	if len(cutoffs) == 1:
+		try:
+			cutoffs = [0, int(cutoffs[0])]
+		except ValueError as e:
+			raise CommandlineError("Quality cutoff value not recognized: {0}".format(e))
+	elif len(cutoffs) == 2:
+		try:
+			cutoffs = [int(cutoffs[0]), int(cutoffs[1])]
+		except ValueError as e:
+			raise CommandlineError("Quality cutoff value not recognized: {0}".format(e))
+	else:
+		raise CommandlineError("Expected one value or two values separated by comma for "
+			"the quality cutoff")
+	return cutoffs
+
+
+def open_output_files(options, default_outfile, interleaved):
 	"""
-	Setup a processing pipeline from parsed command-line options.
+	Return an OutputFiles instance. If demultiplex is True, the untrimmed, untrimmed2, out and out2
+	attributes are not opened files, but paths (out and out2 with the '{name}' template).
+	"""
+	rest_file = info_file = wildcard = None
+	if options.rest_file is not None:
+		rest_file = xopen(options.rest_file, 'w')
+	if options.info_file is not None:
+		info_file = xopen(options.info_file, 'w')
+	if options.wildcard_file is not None:
+		wildcard = xopen(options.wildcard_file, 'w')
 
-	If there are any problems parsing the arguments, a CommandlineError is thrown.
+	def open2(path1, path2):
+		file1 = file2 = None
+		if path1 is not None:
+			file1 = xopen(path1, 'w')
+			if path2 is not None:
+				file2 = xopen(path2, 'w')
+		return file1, file2
+
+	too_short = too_short2 = None
+	if options.minimum_length > 0:
+		too_short, too_short2 = open2(options.too_short_output, options.too_short_paired_output)
+
+	too_long = too_long2 = None
+	if options.maximum_length < sys.maxsize:
+		too_long, too_long2 = open2(options.too_long_output, options.too_long_paired_output)
+
+	if int(options.discard_trimmed) + int(options.discard_untrimmed) + int(
+					options.untrimmed_output is not None) > 1:
+		raise CommandlineError("Only one of the --discard-trimmed, --discard-untrimmed "
+			"and --untrimmed-output options can be used at the same time.")
+
+	demultiplex = options.output is not None and '{name}' in options.output
+	if options.paired_output is not None and (demultiplex != ('{name}' in options.paired_output)):
+		raise CommandlineError('When demultiplexing paired-end data, "{name}" must appear in '
+			'both output file names (-o and -p)')
+
+	if demultiplex:
+		if options.discard_trimmed:
+			raise CommandlineError("Do not use --discard-trimmed when demultiplexing.")
+
+		out = options.output
+		untrimmed = options.output.replace('{name}', 'unknown')
+		if options.untrimmed_output:
+			untrimmed = options.untrimmed_output
+		if options.discard_untrimmed:
+			untrimmed = None
+
+		if options.paired_output is not None:
+			out2 = options.paired_output
+			untrimmed2 = options.paired_output.replace('{name}', 'unknown')
+			if options.untrimmed_paired_output:
+				untrimmed2 = options.untrimmed_paired_output
+			if options.discard_untrimmed:
+				untrimmed2 = None
+
+		else:
+			untrimmed2 = out2 = None
+	else:
+		untrimmed, untrimmed2 = open2(options.untrimmed_output, options.untrimmed_paired_output)
+		out, out2 = open2(options.output, options.paired_output)
+		if out is None:
+			out = default_outfile
+
+	if demultiplex:
+		assert out is not None and '{name}' in out and (out2 is None or '{name}' in out2)
+	return OutputFiles(
+		rest=rest_file,
+		info=info_file,
+		wildcard=wildcard,
+		too_short=too_short,
+		too_short2=too_short2,
+		too_long=too_long,
+		too_long2=too_long2,
+		untrimmed=untrimmed,
+		untrimmed2=untrimmed2,
+		out=out,
+		out2=out2,
+		demultiplex=demultiplex,
+		interleaved=interleaved,
+	)
+
+
+def determine_paired_mode(options):
 	"""
-	if len(args) == 0:
-		raise CommandlineError("At least one parameter needed: name of a FASTA or FASTQ file.")
-	elif len(args) > 2:
-		raise CommandlineError("Too many parameters.")
-	input_filename = args[0]
-	if input_filename.endswith('.qual'):
-		raise CommandlineError("If a .qual file is given, it must be the second argument.")
+	Determine the paired-end mode: single-end, paired-end or legacy paired-end.
+
+	Return False, 'first' or 'both'.
+
+	False -- single-end
+	'first' -- Backwards-compatible "legacy" mode in which read modifications apply only to read 1
+	'both' -- normal paired-end mode in which read modifications apply to read 1 and 2
 
-	# Find out which 'mode' we need to use.
-	# Default: single-read trimming (neither -p nor -A/-G/-B/-U/--interleaved given)
+	Legacy mode is deactivated as soon as any option is used that exists only in cutadapt 1.8 or
+	later, such as -A/-G/-B/-U/--interleaved/--nextseq-trim.
+	"""
 	paired = False
 	if options.paired_output:
-		# Modify first read only, keep second in sync (-p given, but not -A/-G/-B/-U).
-		# This exists for backwards compatibility ('legacy mode').
 		paired = 'first'
-	# Any of these options switch off legacy mode
+
+	# Switch off legacy mode if certain options given
+	if paired and options.nextseq_trim:
+		paired = 'both'
 	if (options.adapters2 or options.front2 or options.anywhere2 or
 			options.cut2 or options.interleaved or options.pair_filter or
 			options.too_short_paired_output or options.too_long_paired_output):
-		# Full paired-end trimming when both -p and -A/-G/-B/-U given
-		# Read modifications (such as quality trimming) are applied also to second read.
 		paired = 'both'
+	return paired
+
 
-	if paired and len(args) == 1 and not options.interleaved:
+def determine_interleaved(options, args):
+	is_interleaved_input = False
+	is_interleaved_output = False
+	if options.interleaved:
+		is_interleaved_input = len(args) == 1
+		is_interleaved_output = not options.paired_output
+		if not is_interleaved_input and not is_interleaved_output:
+			raise CommandlineError("When --interleaved is used, you cannot provide both two "
+				"input files and two output files")
+	return is_interleaved_input, is_interleaved_output
+
+
+def input_files_from_parsed_args(args, paired, interleaved):
+	"""
+	Return tuple (input_filename, input_paired_filename, quality_filename)
+	"""
+	if len(args) == 0:
+		raise CommandlineError("At least one parameter needed: name of a FASTA or FASTQ file.")
+	elif len(args) > 2:
+		raise CommandlineError("Too many parameters.")
+	input_filename = args[0]
+	if input_filename.endswith('.qual'):
+		raise CommandlineError("If a .qual file is given, it must be the second argument.")
+	if paired and len(args) == 1 and not interleaved:
 		raise CommandlineError("When paired-end trimming is enabled via -A/-G/-B/-U/"
 			"--interleaved or -p, two input files are required.")
+
+	input_paired_filename = None
+	quality_filename = None
+	if paired:
+		if not interleaved:
+			input_paired_filename = args[1]
+	elif len(args) == 2:
+		quality_filename = args[1]
+
+	return input_filename, input_paired_filename, quality_filename
+
+
+def pipeline_from_parsed_args(options, paired, pair_filter_mode, quality_filename, is_interleaved_output):
+	"""
+	Setup a processing pipeline from parsed command-line options.
+
+	If there are any problems parsing the arguments, a CommandlineError is thrown.
+
+	Return an instance of Pipeline (SingleEndPipeline or PairedEndPipeline)
+	"""
+
 	if not paired:
 		if options.untrimmed_paired_output:
 			raise CommandlineError("Option --untrimmed-paired-output can only be used when "
 				"trimming paired-end reads (with option -p).")
 
-	interleaved_input = False
-	interleaved_output = False
-	if options.interleaved:
-		interleaved_input = len(args) == 1
-		interleaved_output = not options.paired_output
-		if not interleaved_input and not interleaved_output:
-			raise CommandlineError("When --interleaved is used, you cannot provide both two input files and two output files")
-
-	# Assign input_paired_filename and quality_filename
-	input_paired_filename = None
-	quality_filename = None
 	if paired:
-		if not interleaved_input:
-			input_paired_filename = args[1]
-		if not interleaved_output:
+		if not is_interleaved_output:
 			if not options.paired_output:
 				raise CommandlineError("When paired-end trimming is enabled via -A/-G/-B/-U, "
 					"a second output file needs to be specified via -p (--paired-output).")
@@ -485,112 +529,15 @@ def pipeline_from_parsed_args(options, args, default_outfile):
 		if options.too_long_output and not options.too_long_paired_output:
 			raise CommandlineError("When using --too-long-output with paired-end "
 				"reads, you also need to use --too-long-paired-output")
-	elif len(args) == 2:
-		quality_filename = args[1]
+	elif quality_filename is not None:
 		if options.format is not None:
-			raise CommandlineError("If a pair of .fasta and .qual files is given, the -f/--format parameter cannot be used.")
+			raise CommandlineError('If a pair of .fasta and .qual files is given, the -f/--format '
+				'parameter cannot be used.')
 
 	if options.format is not None and options.format.lower() not in ['fasta', 'fastq', 'sra-fastq']:
 		raise CommandlineError("The input file format must be either 'fasta', 'fastq' or "
 			"'sra-fastq' (not '{0}').".format(options.format))
 
-	# Open input file(s)
-	try:
-		reader = seqio.open(input_filename, file2=input_paired_filename,
-				qualfile=quality_filename, colorspace=options.colorspace,
-				fileformat=options.format, interleaved=interleaved_input)
-	except (seqio.UnknownFileType, IOError) as e:
-		raise CommandlineError(e)
-
-	if options.quality_cutoff is not None:
-		cutoffs = options.quality_cutoff.split(',')
-		if len(cutoffs) == 1:
-			try:
-				cutoffs = [0, int(cutoffs[0])]
-			except ValueError as e:
-				raise CommandlineError("Quality cutoff value not recognized: {0}".format(e))
-		elif len(cutoffs) == 2:
-			try:
-				cutoffs = [int(cutoffs[0]), int(cutoffs[1])]
-			except ValueError as e:
-				raise CommandlineError("Quality cutoff value not recognized: {0}".format(e))
-		else:
-			raise CommandlineError("Expected one value or two values separated by comma for the quality cutoff")
-	else:
-		cutoffs = None
-
-	open_writer = functools.partial(seqio.open, mode='w',
-		qualities=reader.delivers_qualities, colorspace=options.colorspace)
-
-	if options.pair_filter is None:
-		options.pair_filter = 'any'
-	min_affected = 2 if options.pair_filter == 'both' else 1
-	if not paired:
-		filter_wrapper = Redirector
-	elif paired == 'first':
-		filter_wrapper = LegacyPairedRedirector
-	elif paired == 'both':
-		filter_wrapper = functools.partial(PairedRedirector, min_affected=min_affected)
-	filters = []
-	# TODO open_files = []
-	too_short_writer = None  # too short reads go here
-	# TODO pass file name to TooShortReadFilter, add a .close() method?
-	if options.minimum_length > 0:
-		if options.too_short_output:
-			too_short_writer = open_writer(options.too_short_output, options.too_short_paired_output)
-		filters.append(filter_wrapper(too_short_writer, TooShortReadFilter(options.minimum_length)))
-	too_long_writer = None  # too long reads go here
-	if options.maximum_length < sys.maxsize:
-		if options.too_long_output is not None:
-			too_long_writer = open_writer(options.too_long_output, options.too_long_paired_output)
-		filters.append(filter_wrapper(too_long_writer, TooLongReadFilter(options.maximum_length)))
-
-	if options.max_n != -1:
-		filters.append(filter_wrapper(None, NContentFilter(options.max_n)))
-
-	if int(options.discard_trimmed) + int(options.discard_untrimmed) + int(options.untrimmed_output is not None) > 1:
-		raise CommandlineError("Only one of the --discard-trimmed, --discard-untrimmed "
-			"and --untrimmed-output options can be used at the same time.")
-	demultiplexer = None
-	untrimmed_writer = None
-	writer = None
-	if options.output is not None and '{name}' in options.output:
-		if options.discard_trimmed:
-			raise CommandlineError("Do not use --discard-trimmed when demultiplexing.")
-		if paired:
-			raise CommandlineError("Demultiplexing not supported for paired-end files, yet.")
-		untrimmed = options.output.replace('{name}', 'unknown')
-		if options.untrimmed_output:
-			untrimmed = options.untrimmed_output
-		if options.discard_untrimmed:
-			untrimmed = None
-		demultiplexer = Demultiplexer(options.output, untrimmed,
-			qualities=reader.delivers_qualities, colorspace=options.colorspace)
-		filters.append(demultiplexer)
-	else:
-		# Set up the remaining filters to deal with --discard-trimmed,
-		# --discard-untrimmed and --untrimmed-output. These options
-		# are mutually exclusive in order to avoid brain damage.
-		if options.discard_trimmed:
-			filters.append(filter_wrapper(None, DiscardTrimmedFilter()))
-		elif options.discard_untrimmed:
-			filters.append(filter_wrapper(None, DiscardUntrimmedFilter()))
-		elif options.untrimmed_output:
-			untrimmed_writer = open_writer(options.untrimmed_output,
-				options.untrimmed_paired_output)
-			filters.append(filter_wrapper(untrimmed_writer, DiscardUntrimmedFilter()))
-
-		# Finally, figure out where the reads that passed all the previous
-		# filters should go.
-		if options.output is not None:
-			writer = open_writer(options.output, options.paired_output, interleaved=interleaved_output)
-		else:
-			writer = open_writer(default_outfile, interleaved=interleaved_output)
-		if not paired:
-			filters.append(NoFilter(writer))
-		else:
-			filters.append(PairedNoFilter(writer))
-
 	if options.maq:
 		options.colorspace = True
 		options.double_encode = True
@@ -604,21 +551,14 @@ def pipeline_from_parsed_args(options, args, default_outfile):
 	if options.double_encode and not options.colorspace:
 		raise CommandlineError("Double-encoding makes only sense in colorspace.")
 	if options.anywhere and options.colorspace:
-		raise CommandlineError("Using --anywhere with colorspace reads is currently not supported (if you think this may be useful, contact the author).")
+		raise CommandlineError("Using --anywhere with colorspace reads is currently not supported "
+			"(if you think this may be useful, contact the author).")
 	if not (0 <= options.error_rate <= 1.):
 		raise CommandlineError("The maximum error rate must be between 0 and 1.")
 	if options.overlap < 1:
 		raise CommandlineError("The overlap must be at least 1.")
-
-	if options.rest_file is not None:
-		options.rest_file = xopen(options.rest_file, 'w')
-		rest_writer = RestFileWriter(options.rest_file)
-	else:
-		rest_writer = None
-	if options.info_file is not None:
-		options.info_file = xopen(options.info_file, 'w')
-	if options.wildcard_file is not None:
-		options.wildcard_file = xopen(options.wildcard_file, 'w')
+	if not (0 <= options.gc_content <= 100):
+		raise CommandlineError("GC content must be given as percentage between 0 and 100")
 
 	if options.colorspace:
 		if options.match_read_wildcards:
@@ -646,8 +586,14 @@ def pipeline_from_parsed_args(options, args, default_outfile):
 		for adapter in adapters + adapters2:
 			adapter.enable_debug()
 
-	# Create the single-end processing pipeline (a list of "modifiers")
-	modifiers = []
+	# Create the processing pipeline.
+	# If no second-read adapters were given (via -A/-G/-B/-U), we need to
+	# be backwards compatible and *no modifications* are done to the second read.
+	if paired:
+		pipeline = PairedEndPipeline(pair_filter_mode, modify_first_read_only=paired == 'first')
+	else:
+		pipeline = SingleEndPipeline()
+
 	if options.cut:
 		if len(options.cut) > 2:
 			raise CommandlineError("You cannot remove bases from more than two ends.")
@@ -655,89 +601,63 @@ def pipeline_from_parsed_args(options, args, default_outfile):
 			raise CommandlineError("You cannot remove bases from the same end twice.")
 		for cut in options.cut:
 			if cut != 0:
-				modifiers.append(UnconditionalCutter(cut))
+				pipeline.add1(UnconditionalCutter(cut))
+
+	if options.cut2:
+		if len(options.cut2) > 2:
+			raise CommandlineError("You cannot remove bases from more than two ends.")
+		if len(options.cut2) == 2 and options.cut2[0] * options.cut2[1] > 0:
+			raise CommandlineError("You cannot remove bases from the same end twice.")
+		for cut in options.cut2:
+			if cut != 0:
+				pipeline.add2(UnconditionalCutter(cut))
 
 	if options.nextseq_trim is not None:
-		modifiers.append(NextseqQualityTrimmer(options.nextseq_trim, options.quality_base))
+		pipeline.add(NextseqQualityTrimmer(options.nextseq_trim, options.quality_base))
+	if options.quality_cutoff is not None:
+		cutoffs = parse_cutoffs(options.quality_cutoff)
+		pipeline.add(QualityTrimmer(cutoffs[0], cutoffs[1], options.quality_base))
 
-	if cutoffs:
-		modifiers.append(QualityTrimmer(cutoffs[0], cutoffs[1], options.quality_base))
 	if adapters:
-		adapter_cutter = AdapterCutter(adapters, options.times,
-				options.wildcard_file, options.info_file,
-				rest_writer, options.action)
-		modifiers.append(adapter_cutter)
+		adapter_cutter = AdapterCutter(adapters, options.times, options.action)
+		pipeline.add1(adapter_cutter)
+		pipeline.n_adapters += len(adapters)
+	if adapters2:
+		adapter_cutter2 = AdapterCutter(adapters2, options.times, options.action)
+		pipeline.add2(adapter_cutter2)
+		pipeline.n_adapters += len(adapters2)
 
 	# Modifiers that apply to both reads of paired-end reads unless in legacy mode
-	modifiers_both = []
 	if options.length is not None:
-		modifiers_both.append(Shortener(options.length))
+		pipeline.add(Shortener(options.length))
 	if options.trim_n:
-		modifiers_both.append(NEndTrimmer())
+		pipeline.add(NEndTrimmer())
 	if options.length_tag:
-		modifiers_both.append(LengthTagModifier(options.length_tag))
+		pipeline.add(LengthTagModifier(options.length_tag))
 	if options.strip_f3:
 		options.strip_suffix.append('_F3')
 	for suffix in options.strip_suffix:
-		modifiers_both.append(SuffixRemover(suffix))
+		pipeline.add(SuffixRemover(suffix))
 	if options.prefix or options.suffix:
-		modifiers_both.append(PrefixSuffixAdder(options.prefix, options.suffix))
+		pipeline.add(PrefixSuffixAdder(options.prefix, options.suffix))
 	if options.double_encode:
-		modifiers_both.append(DoubleEncoder())
-	if options.zero_cap and reader.delivers_qualities:
-		modifiers_both.append(ZeroCapper(quality_base=options.quality_base))
+		pipeline.add(DoubleEncoder())
+	if options.zero_cap:
+		pipeline.add(ZeroCapper(quality_base=options.quality_base))
 	if options.trim_primer:
-		modifiers_both.append(PrimerTrimmer)
-	modifiers.extend(modifiers_both)
+		pipeline.add(PrimerTrimmer)
 
-	# For paired-end data, create a second processing pipeline.
-	# However, if no second-read adapters were given (via -A/-G/-B/-U), we need to
-	# be backwards compatible and *no modifications* are done to the second read.
-	modifiers2 = []
-	if paired == 'both':
-		if options.cut2:
-			if len(options.cut2) > 2:
-				raise CommandlineError("You cannot remove bases from more than two ends.")
-			if len(options.cut2) == 2 and options.cut2[0] * options.cut2[1] > 0:
-				raise CommandlineError("You cannot remove bases from the same end twice.")
-			for cut in options.cut2:
-				if cut != 0:
-					modifiers2.append(UnconditionalCutter(cut))
-
-		if cutoffs:
-			modifiers2.append(QualityTrimmer(cutoffs[0], cutoffs[1], options.quality_base))
-		if adapters2:
-			adapter_cutter2 = AdapterCutter(adapters2, options.times,
-					None, None, None, options.action)
-			modifiers2.append(adapter_cutter2)
-		modifiers2.extend(modifiers_both)
-
-	if paired:
-		pipeline = PairedEndPipeline(reader, modifiers, modifiers2, filters)
-	else:
-		pipeline = SingleEndPipeline(reader, modifiers, filters)
-
-	# TODO the following should be done some other way
-	pipeline.paired = paired
-	pipeline.error_rate = options.error_rate
-	pipeline.n_adapters = len(adapters) + len(adapters2)
-	pipeline.should_print_warning = paired == 'first' and (modifiers_both or cutoffs)
-	for f in [writer, untrimmed_writer,
-			options.rest_file, options.wildcard_file,
-			options.info_file, too_short_writer, too_long_writer,
-			options.info_file, demultiplexer]:
-		pipeline.register_file_to_close(f)
 	return pipeline
 
 
 def main(cmdlineargs=None, default_outfile=sys.stdout):
 	"""
-	Main function that evaluates command-line parameters and iterates
-	over all reads.
+	Main function that sets up a processing pipeline and runs it.
 
 	default_outfile is the file to which trimmed reads are sent if the ``-o``
 	parameter is not used.
 	"""
+	start_time = time.time()
 	parser = get_option_parser()
 	if cmdlineargs is None:
 		cmdlineargs = sys.argv[1:]
@@ -747,47 +667,97 @@ def main(cmdlineargs=None, default_outfile=sys.stdout):
 	if not logging.root.handlers:
 		setup_logging(stdout=bool(options.output), quiet=options.quiet)
 
-	if not 0 <= options.gc_content <= 100:
-		parser.error("GC content must be given as percentage between 0 and 100")
+	paired = determine_paired_mode(options)
+	assert paired in (False, 'first', 'both')
+
+	if paired == 'first':
+		# legacy mode
+		assert options.pair_filter is None
+		pair_filter_mode = 'first'
+	elif options.pair_filter is None:
+		# default
+		pair_filter_mode = 'any'
+	else:
+		# user-provided behavior
+		pair_filter_mode = options.pair_filter
+
 	try:
-		pipeline = pipeline_from_parsed_args(options, args, default_outfile)
+		is_interleaved_input, is_interleaved_output = determine_interleaved(options, args)
+		input_filename, input_paired_filename, quality_filename = input_files_from_parsed_args(args,
+			paired, is_interleaved_input)
+		pipeline = pipeline_from_parsed_args(options, paired, pair_filter_mode, quality_filename, is_interleaved_output)
+		outfiles = open_output_files(options, default_outfile, is_interleaved_output)
 	except CommandlineError as e:
 		parser.error(e)
+		return  # avoid IDE warnings below
+
+	cores = max(1, options.cores)
+	if cores > 1:
+		if (
+			PY3
+			and ParallelPipelineRunner.can_output_to(outfiles)
+			and quality_filename is None
+			and not options.colorspace
+			and options.format is None
+			and cores > 1
+		):
+			runner = ParallelPipelineRunner(pipeline, cores, options.buffer_size)
+		else:
+			if not PY3:
+				logger.error('Running in parallel is not supported on Python 2')
+			else:
+				logger.error('Running in parallel is currently not supported for '
+					'the given combination of command-line parameters.')
+			sys.exit(1)
+	else:
+		runner = pipeline
+	try:
+		runner.set_input(input_filename, file2=input_paired_filename,
+			qualfile=quality_filename, colorspace=options.colorspace,
+			fileformat=options.format, interleaved=is_interleaved_input)
+		runner.set_output(outfiles, options.minimum_length, options.maximum_length, options.max_n,
+			options.discard_trimmed, options.discard_untrimmed)
+	except (seqio.UnknownFileType, IOError) as e:
+		parser.error(e)
 
 	implementation = platform.python_implementation()
 	opt = ' (' + implementation + ')' if implementation != 'CPython' else ''
 	logger.info("This is cutadapt %s with Python %s%s", __version__,
 		platform.python_version(), opt)
 	logger.info("Command line parameters: %s", " ".join(cmdlineargs))
+	logger.info('Running on %d core%s', cores, 's' if cores > 1 else '')
 	logger.info("Trimming %s adapter%s with at most %.1f%% errors in %s mode ...",
 		pipeline.n_adapters, 's' if pipeline.n_adapters != 1 else '',
-		pipeline.error_rate * 100,
-		{ False: 'single-end', 'first': 'paired-end legacy', 'both': 'paired-end' }[pipeline.paired])
+		options.error_rate * 100,
+		{False: 'single-end', 'first': 'paired-end legacy', 'both': 'paired-end'}[pipeline.paired])
 
-	if pipeline.should_print_warning:
-		logger.warning('\n'.join(textwrap.wrap('WARNING: Requested read '
-			'modifications are applied only to the first '
-			'read since backwards compatibility mode is enabled. '
-			'To modify both reads, also use any of the -A/-B/-G/-U options. '
-			'Use a dummy adapter sequence when necessary: -A XXX')))
+	if pipeline.should_warn_legacy:
+		logger.warning('\n'.join(textwrap.wrap('Legacy mode is '
+			'enabled. Read modification and filtering options *ignore* '
+			'the second read. To switch to regular paired-end mode, '
+			'provide the --pair-filter=any option or use any of the '
+			'-A/-B/-G/-U/--interleaved options.')))
 
 	try:
-		stats = pipeline.run()
-	except KeyboardInterrupt as e:
+		stats = runner.run()
+		# cProfile.runctx('stats=runner.run()', globals(), locals(), 'profile_main.prof')
+		runner.close()
+	except KeyboardInterrupt:
 		print("Interrupted", file=sys.stderr)
 		sys.exit(130)
 	except IOError as e:
 		if e.errno == errno.EPIPE:
 			sys.exit(1)
 		raise
-	except (seqio.FormatError, EOFError) as e:
+	except (seqio.FormatError, seqio.UnknownFileType, EOFError) as e:
 		sys.exit("cutadapt: error: {0}".format(e))
 
+	elapsed = time.time() - start_time
 	if not options.quiet:
 		# send statistics to stderr if result was sent to stdout
 		stat_file = sys.stderr if options.output is None else None
 		with redirect_standard_output(stat_file):
-			print_report(stats, options.gc_content / 100)
+			print_report(stats, elapsed, options.gc_content / 100)
 
 
 if __name__ == '__main__':
diff --git a/cutadapt/_align.c b/src/cutadapt/_align.c
similarity index 88%
rename from cutadapt/_align.c
rename to src/cutadapt/_align.c
index 414667f..5815b19 100644
--- a/cutadapt/_align.c
+++ b/src/cutadapt/_align.c
@@ -1,11 +1,10 @@
-/* Generated by Cython 0.25.2 */
+/* Generated by Cython 0.24 */
 
 /* BEGIN: Cython Metadata
 {
     "distutils": {
         "depends": []
-    },
-    "module_name": "cutadapt._align"
+    }
 }
 END: Cython Metadata */
 
@@ -16,7 +15,7 @@ END: Cython Metadata */
 #elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03020000)
     #error Cython requires Python 2.6+ or Python 3.2+.
 #else
-#define CYTHON_ABI "0_25_2"
+#define CYTHON_ABI "0_24"
 #include <stddef.h>
 #ifndef offsetof
   #define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
@@ -38,11 +37,6 @@ END: Cython Metadata */
 #ifndef DL_EXPORT
   #define DL_EXPORT(t) t
 #endif
-#ifndef HAVE_LONG_LONG
-  #if PY_VERSION_HEX >= 0x03030000 || (PY_MAJOR_VERSION == 2 && PY_VERSION_HEX >= 0x02070000)
-    #define HAVE_LONG_LONG
-  #endif
-#endif
 #ifndef PY_LONG_LONG
   #define PY_LONG_LONG LONG_LONG
 #endif
@@ -51,110 +45,13 @@ END: Cython Metadata */
 #endif
 #ifdef PYPY_VERSION
   #define CYTHON_COMPILING_IN_PYPY 1
-  #define CYTHON_COMPILING_IN_PYSTON 0
   #define CYTHON_COMPILING_IN_CPYTHON 0
-  #undef CYTHON_USE_TYPE_SLOTS
-  #define CYTHON_USE_TYPE_SLOTS 0
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_INTERNALS
-  #define CYTHON_USE_UNICODE_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #undef CYTHON_AVOID_BORROWED_REFS
-  #define CYTHON_AVOID_BORROWED_REFS 1
-  #undef CYTHON_ASSUME_SAFE_MACROS
-  #define CYTHON_ASSUME_SAFE_MACROS 0
-  #undef CYTHON_UNPACK_METHODS
-  #define CYTHON_UNPACK_METHODS 0
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
-#elif defined(PYSTON_VERSION)
-  #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 1
-  #define CYTHON_COMPILING_IN_CPYTHON 0
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
 #else
   #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 0
   #define CYTHON_COMPILING_IN_CPYTHON 1
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #if PY_MAJOR_VERSION < 3
-    #undef CYTHON_USE_ASYNC_SLOTS
-    #define CYTHON_USE_ASYNC_SLOTS 0
-  #elif !defined(CYTHON_USE_ASYNC_SLOTS)
-    #define CYTHON_USE_ASYNC_SLOTS 1
-  #endif
-  #if PY_VERSION_HEX < 0x02070000
-    #undef CYTHON_USE_PYLONG_INTERNALS
-    #define CYTHON_USE_PYLONG_INTERNALS 0
-  #elif !defined(CYTHON_USE_PYLONG_INTERNALS)
-    #define CYTHON_USE_PYLONG_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_PYLIST_INTERNALS
-    #define CYTHON_USE_PYLIST_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #if PY_VERSION_HEX < 0x030300F0
-    #undef CYTHON_USE_UNICODE_WRITER
-    #define CYTHON_USE_UNICODE_WRITER 0
-  #elif !defined(CYTHON_USE_UNICODE_WRITER)
-    #define CYTHON_USE_UNICODE_WRITER 1
-  #endif
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #ifndef CYTHON_FAST_THREAD_STATE
-    #define CYTHON_FAST_THREAD_STATE 1
-  #endif
-  #ifndef CYTHON_FAST_PYCALL
-    #define CYTHON_FAST_PYCALL 1
-  #endif
 #endif
-#if !defined(CYTHON_FAST_PYCCALL)
-#define CYTHON_FAST_PYCCALL  (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1)
+#if !defined(CYTHON_USE_PYLONG_INTERNALS) && CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x02070000
+  #define CYTHON_USE_PYLONG_INTERNALS 1
 #endif
 #if CYTHON_USE_PYLONG_INTERNALS
   #include "longintrepr.h"
@@ -190,44 +87,24 @@ END: Cython Metadata */
 #ifndef Py_TPFLAGS_HAVE_FINALIZE
   #define Py_TPFLAGS_HAVE_FINALIZE 0
 #endif
-#ifndef METH_FASTCALL
-  #define METH_FASTCALL 0x80
-  typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject **args,
-                                              Py_ssize_t nargs, PyObject *kwnames);
-#else
-  #define __Pyx_PyCFunctionFast _PyCFunctionFast
-#endif
-#if CYTHON_FAST_PYCCALL
-#define __Pyx_PyFastCFunction_Check(func)\
-    ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)))))
-#else
-#define __Pyx_PyFastCFunction_Check(func) 0
-#endif
 #if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
   #define CYTHON_PEP393_ENABLED 1
   #define __Pyx_PyUnicode_READY(op)       (likely(PyUnicode_IS_READY(op)) ?\
                                               0 : _PyUnicode_Ready((PyObject *)(op)))
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_LENGTH(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i)
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   PyUnicode_MAX_CHAR_VALUE(u)
   #define __Pyx_PyUnicode_KIND(u)         PyUnicode_KIND(u)
   #define __Pyx_PyUnicode_DATA(u)         PyUnicode_DATA(u)
   #define __Pyx_PyUnicode_READ(k, d, i)   PyUnicode_READ(k, d, i)
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  PyUnicode_WRITE(k, d, i, ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u)))
 #else
   #define CYTHON_PEP393_ENABLED 0
-  #define PyUnicode_1BYTE_KIND  1
-  #define PyUnicode_2BYTE_KIND  2
-  #define PyUnicode_4BYTE_KIND  4
   #define __Pyx_PyUnicode_READY(op)       (0)
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_SIZE(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i]))
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111)
   #define __Pyx_PyUnicode_KIND(u)         (sizeof(Py_UNICODE))
   #define __Pyx_PyUnicode_DATA(u)         ((void*)PyUnicode_AS_UNICODE(u))
   #define __Pyx_PyUnicode_READ(k, d, i)   ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i]))
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  (((void)(k)), ((Py_UNICODE*)d)[i] = ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != PyUnicode_GET_SIZE(u))
 #endif
 #if CYTHON_COMPILING_IN_PYPY
@@ -241,9 +118,6 @@ END: Cython Metadata */
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains)
   #define PyUnicode_Contains(u, s)  PySequence_Contains(u, s)
 #endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check)
-  #define PyByteArray_Check(obj)  PyObject_TypeCheck(obj, &PyByteArray_Type)
-#endif
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format)
   #define PyObject_Format(obj, fmt)  PyObject_CallMethod(obj, "__format__", "O", fmt)
 #endif
@@ -252,13 +126,6 @@ END: Cython Metadata */
   #define PyObject_Free(p)     PyMem_Free(p)
   #define PyObject_Realloc(p)  PyMem_Realloc(p)
 #endif
-#if CYTHON_COMPILING_IN_PYSTON
-  #define __Pyx_PyCode_HasFreeVars(co)  PyCode_HasFreeVars(co)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno)
-#else
-  #define __Pyx_PyCode_HasFreeVars(co)  (PyCode_GetNumFree(co) > 0)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno)  (frame)->f_lineno = (lineno)
-#endif
 #define __Pyx_PyString_FormatSafe(a, b)   ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b))
 #define __Pyx_PyUnicode_FormatSafe(a, b)  ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b))
 #if PY_MAJOR_VERSION >= 3
@@ -287,7 +154,6 @@ END: Cython Metadata */
   #define PySet_CheckExact(obj)        (Py_TYPE(obj) == &PySet_Type)
 #endif
 #define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type)
-#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception)
 #if PY_MAJOR_VERSION >= 3
   #define PyIntObject                  PyLongObject
   #define PyInt_Type                   PyLong_Type
@@ -326,20 +192,18 @@ END: Cython Metadata */
 #else
   #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
 #endif
-#if CYTHON_USE_ASYNC_SLOTS
-  #if PY_VERSION_HEX >= 0x030500B1
-    #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
-    #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
-  #else
-    typedef struct {
-        unaryfunc am_await;
-        unaryfunc am_aiter;
-        unaryfunc am_anext;
-    } __Pyx_PyAsyncMethodsStruct;
-    #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
-  #endif
+#if PY_VERSION_HEX >= 0x030500B1
+#define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
+#define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
+#elif CYTHON_COMPILING_IN_CPYTHON && PY_MAJOR_VERSION >= 3
+typedef struct {
+    unaryfunc am_await;
+    unaryfunc am_aiter;
+    unaryfunc am_anext;
+} __Pyx_PyAsyncMethodsStruct;
+#define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
 #else
-  #define __Pyx_PyType_AsAsync(obj) NULL
+#define __Pyx_PyType_AsAsync(obj) NULL
 #endif
 #ifndef CYTHON_RESTRICT
   #if defined(__GNUC__)
@@ -352,39 +216,10 @@ END: Cython Metadata */
     #define CYTHON_RESTRICT
   #endif
 #endif
-#ifndef CYTHON_UNUSED
-# if defined(__GNUC__)
-#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
-#     define CYTHON_UNUSED __attribute__ ((__unused__))
-#   else
-#     define CYTHON_UNUSED
-#   endif
-# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
-#   define CYTHON_UNUSED __attribute__ ((__unused__))
-# else
-#   define CYTHON_UNUSED
-# endif
-#endif
-#ifndef CYTHON_MAYBE_UNUSED_VAR
-#  if defined(__cplusplus)
-     template<class T> void CYTHON_MAYBE_UNUSED_VAR( const T& ) { }
-#  else
-#    define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x)
-#  endif
-#endif
-#ifndef CYTHON_NCP_UNUSED
-# if CYTHON_COMPILING_IN_CPYTHON
-#  define CYTHON_NCP_UNUSED
-# else
-#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
-# endif
-#endif
 #define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None)
 
 #ifndef CYTHON_INLINE
-  #if defined(__clang__)
-    #define CYTHON_INLINE __inline__ __attribute__ ((__unused__))
-  #elif defined(__GNUC__)
+  #if defined(__GNUC__)
     #define CYTHON_INLINE __inline__
   #elif defined(_MSC_VER)
     #define CYTHON_INLINE __inline
@@ -408,11 +243,6 @@ static CYTHON_INLINE float __PYX_NAN() {
   return value;
 }
 #endif
-#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL)
-#define __Pyx_truncl trunc
-#else
-#define __Pyx_truncl truncl
-#endif
 
 
 #define __PYX_ERR(f_index, lineno, Ln_error) \
@@ -446,6 +276,26 @@ static CYTHON_INLINE float __PYX_NAN() {
 #define CYTHON_WITHOUT_ASSERTIONS
 #endif
 
+#ifndef CYTHON_UNUSED
+# if defined(__GNUC__)
+#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
+#     define CYTHON_UNUSED __attribute__ ((__unused__))
+#   else
+#     define CYTHON_UNUSED
+#   endif
+# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
+#   define CYTHON_UNUSED __attribute__ ((__unused__))
+# else
+#   define CYTHON_UNUSED
+# endif
+#endif
+#ifndef CYTHON_NCP_UNUSED
+# if CYTHON_COMPILING_IN_CPYTHON
+#  define CYTHON_NCP_UNUSED
+# else
+#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
+# endif
+#endif
 typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding;
                 const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry;
 
@@ -523,7 +373,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*);
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x);
 static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*);
 static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t);
-#if CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x))
 #else
 #define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x)
@@ -633,7 +483,7 @@ static const char *__pyx_filename;
 
 
 static const char *__pyx_f[] = {
-  "cutadapt/_align.pyx",
+  "src/cutadapt/_align.pyx",
 };
 
 /*--- Type declarations ---*/
@@ -770,7 +620,7 @@ struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct_2_genexpr {
 #define __Pyx_XCLEAR(r)   do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0)
 
 /* PyObjectGetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name) {
     PyTypeObject* tp = Py_TYPE(obj);
     if (likely(tp->tp_getattro))
@@ -814,9 +664,7 @@ static PyObject* __Pyx__CallUnboundCMethod0(__Pyx_CachedCFunction* cfunc, PyObje
     ((likely((cfunc)->func)) ?\
         (likely((cfunc)->flag == METH_NOARGS) ?  (*((cfunc)->func))(self, NULL) :\
          (likely((cfunc)->flag == (METH_VARARGS | METH_KEYWORDS)) ?  ((*(PyCFunctionWithKeywords)(cfunc)->func)(self, __pyx_empty_tuple, NULL)) :\
-             ((cfunc)->flag == METH_VARARGS ?  (*((cfunc)->func))(self, __pyx_empty_tuple) :\
-              (PY_VERSION_HEX >= 0x030600B1 && (cfunc)->flag == METH_FASTCALL ?  (*(__Pyx_PyCFunctionFast)(cfunc)->func)(self, &PyTuple_GET_ITEM(__pyx_empty_tuple, 0), 0, NULL) :\
-                __Pyx__CallUnboundCMethod0(cfunc, self))))) :\
+             ((cfunc)->flag == METH_VARARGS ?  (*((cfunc)->func))(self, __pyx_empty_tuple) : __Pyx__CallUnboundCMethod0(cfunc, self)))) :\
         __Pyx__CallUnboundCMethod0(cfunc, self))
 #else
 #define __Pyx_CallUnboundCMethod0(cfunc, self)  __Pyx__CallUnboundCMethod0(cfunc, self)
@@ -854,24 +702,6 @@ static long __Pyx__PyObject_Ord(PyObject* c);
 static CYTHON_INLINE int __Pyx_SetItemInt_ByteArray_Fast(PyObject* string, Py_ssize_t i, unsigned char v,
                                                          int wraparound, int boundscheck);
 
-/* PyCFunctionFastCall.proto */
-#if CYTHON_FAST_PYCCALL
-static CYTHON_INLINE PyObject *__Pyx_PyCFunction_FastCall(PyObject *func, PyObject **args, Py_ssize_t nargs);
-#else
-#define __Pyx_PyCFunction_FastCall(func, args, nargs)  (assert(0), NULL)
-#endif
-
-/* PyFunctionFastCall.proto */
-#if CYTHON_FAST_PYCALL
-#define __Pyx_PyFunction_FastCall(func, args, nargs)\
-    __Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL)
-#if 1 || PY_VERSION_HEX < 0x030600B1
-static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs);
-#else
-#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs)
-#endif
-#endif
-
 /* PyObjectCallMethO.proto */
 #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg);
@@ -900,7 +730,7 @@ static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\
     const char* function_name);
 
 /* PyIntBinop.proto */
-#if !CYTHON_COMPILING_IN_PYPY
+#if CYTHON_COMPILING_IN_CPYTHON
 static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, long intval, int inplace);
 #else
 #define __Pyx_PyInt_AddObjC(op1, op2, intval, inplace)\
@@ -908,7 +738,7 @@ static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, long intval,
 #endif
 
 /* ListCompAppend.proto */
-#if CYTHON_USE_PYLIST_INTERNALS && CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE int __Pyx_ListComp_Append(PyObject* list, PyObject* x) {
     PyListObject* L = (PyListObject*) list;
     Py_ssize_t len = Py_SIZE(list);
@@ -925,7 +755,7 @@ static CYTHON_INLINE int __Pyx_ListComp_Append(PyObject* list, PyObject* x) {
 #endif
 
 /* PyObjectSetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_PyObject_DelAttrStr(o,n) __Pyx_PyObject_SetAttrStr(o,n,NULL)
 static CYTHON_INLINE int __Pyx_PyObject_SetAttrStr(PyObject* obj, PyObject* attr_name, PyObject* value) {
     PyTypeObject* tp = Py_TYPE(obj);
@@ -996,7 +826,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyBytes_Join(PyObject* sep, PyObject* value
 #endif
 
 /* ListAppend.proto */
-#if CYTHON_USE_PYLIST_INTERNALS && CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE int __Pyx_PyList_Append(PyObject* list, PyObject* x) {
     PyListObject* L = (PyListObject*) list;
     Py_ssize_t len = Py_SIZE(list);
@@ -1017,7 +847,7 @@ static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, in
     const char *name, int exact);
 
 /* PyThreadStateGet.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_PyThreadState_declare  PyThreadState *__pyx_tstate;
 #define __Pyx_PyThreadState_assign  __pyx_tstate = PyThreadState_GET();
 #else
@@ -1026,7 +856,7 @@ static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, in
 #endif
 
 /* PyErrFetchRestore.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_ErrRestoreWithState(type, value, tb)  __Pyx_ErrRestoreInState(PyThreadState_GET(), type, value, tb)
 #define __Pyx_ErrFetchWithState(type, value, tb)    __Pyx_ErrFetchInState(PyThreadState_GET(), type, value, tb)
 #define __Pyx_ErrRestore(type, value, tb)  __Pyx_ErrRestoreInState(__pyx_tstate, type, value, tb)
@@ -1155,7 +985,7 @@ static CYTHON_INLINE unsigned char __Pyx_PyInt_As_unsigned_char(PyObject *);
 static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *);
 
 /* SwapException.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_ExceptionSwap(type, value, tb)  __Pyx__ExceptionSwap(__pyx_tstate, type, value, tb)
 static CYTHON_INLINE void __Pyx__ExceptionSwap(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
 #else
@@ -1179,13 +1009,11 @@ typedef struct {
     PyObject *yieldfrom;
     PyObject *gi_name;
     PyObject *gi_qualname;
-    PyObject *gi_modulename;
     int resume_label;
     char is_running;
 } __pyx_CoroutineObject;
-static __pyx_CoroutineObject *__Pyx__Coroutine_New(
-    PyTypeObject *type, __pyx_coroutine_body_t body, PyObject *closure,
-    PyObject *name, PyObject *qualname, PyObject *module_name);
+static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject *type, __pyx_coroutine_body_t body,
+                                                   PyObject *closure, PyObject *name, PyObject *qualname);
 static int __Pyx_Coroutine_clear(PyObject *self);
 #if 1 || PY_VERSION_HEX < 0x030300B0
 static int __Pyx_PyGen_FetchStopIterationValue(PyObject **pvalue);
@@ -1203,8 +1031,8 @@ static int __Pyx_patch_abc(void);
 #define __Pyx_Generator_USED
 static PyTypeObject *__pyx_GeneratorType = 0;
 #define __Pyx_Generator_CheckExact(obj) (Py_TYPE(obj) == __pyx_GeneratorType)
-#define __Pyx_Generator_New(body, closure, name, qualname, module_name)\
-    __Pyx__Coroutine_New(__pyx_GeneratorType, body, closure, name, qualname, module_name)
+#define __Pyx_Generator_New(body, closure, name, qualname)\
+    __Pyx__Coroutine_New(__pyx_GeneratorType, body, closure, name, qualname)
 static PyObject *__Pyx_Generator_Next(PyObject *self);
 static int __pyx_Generator_init(void);
 
@@ -1322,11 +1150,15 @@ static const char __pyx_k_wildcard_query[] = "wildcard_query";
 static const char __pyx_k_DPMatrix___init[] = "DPMatrix.__init__";
 static const char __pyx_k_cutadapt__align[] = "cutadapt._align";
 static const char __pyx_k_compare_prefixes[] = "compare_prefixes";
+static const char __pyx_k_STOP_WITHIN_QUERY[] = "STOP_WITHIN_QUERY";
 static const char __pyx_k_DPMatrix_set_entry[] = "DPMatrix.set_entry";
+static const char __pyx_k_START_WITHIN_QUERY[] = "START_WITHIN_QUERY";
+static const char __pyx_k_STOP_WITHIN_REFERENCE[] = "STOP_WITHIN_REFERENCE";
+static const char __pyx_k_START_WITHIN_REFERENCE[] = "START_WITHIN_REFERENCE";
 static const char __pyx_k_DPMatrix___str___locals_genexpr[] = "DPMatrix.__str__.<locals>.genexpr";
 static const char __pyx_k_Insertion_deletion_cost_must_be[] = "Insertion/deletion cost must be at leat 1";
 static const char __pyx_k_Representation_of_the_dynamic_p[] = "\n\tRepresentation of the dynamic-programming matrix.\n\n\tThis used only when debugging is enabled in the Aligner class since the\n\tmatrix is normally not stored in full.\n\n\tEntries in the matrix may be None, in which case that value was not\n\tcomputed.\n\t";
-static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/_align.pyx";
+static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/src/cutadapt/_align.pyx";
 static const char __pyx_k_Minimum_overlap_must_be_at_least[] = "Minimum overlap must be at least 1";
 static PyObject *__pyx_kp_b_;
 static PyObject *__pyx_kp_s_0_2d;
@@ -1350,6 +1182,10 @@ static PyObject *__pyx_n_s_N;
 static PyObject *__pyx_n_s_R;
 static PyObject *__pyx_kp_s_Representation_of_the_dynamic_p;
 static PyObject *__pyx_n_s_S;
+static PyObject *__pyx_n_s_START_WITHIN_QUERY;
+static PyObject *__pyx_n_s_START_WITHIN_REFERENCE;
+static PyObject *__pyx_n_s_STOP_WITHIN_QUERY;
+static PyObject *__pyx_n_s_STOP_WITHIN_REFERENCE;
 static PyObject *__pyx_n_s_T;
 static PyObject *__pyx_n_s_U;
 static PyObject *__pyx_n_s_V;
@@ -1547,7 +1383,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align__acgt_table(CYTHON_UNUSED PyObject *
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
         if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_2); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 35, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 35, __pyx_L1_error)
@@ -1555,7 +1391,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align__acgt_table(CYTHON_UNUSED PyObject *
         #endif
       } else {
         if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_2); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 35, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 35, __pyx_L1_error)
@@ -1576,7 +1412,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align__acgt_table(CYTHON_UNUSED PyObject *
     }
     if ((likely(PyTuple_CheckExact(__pyx_t_2))) || (PyList_CheckExact(__pyx_t_2))) {
       PyObject* sequence = __pyx_t_2;
-      #if !CYTHON_COMPILING_IN_PYPY
+      #if CYTHON_COMPILING_IN_CPYTHON
       Py_ssize_t size = Py_SIZE(sequence);
       #else
       Py_ssize_t size = PySequence_Size(sequence);
@@ -1586,7 +1422,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align__acgt_table(CYTHON_UNUSED PyObject *
         else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
         __PYX_ERR(0, 35, __pyx_L1_error)
       }
-      #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+      #if CYTHON_COMPILING_IN_CPYTHON
       if (likely(PyTuple_CheckExact(sequence))) {
         __pyx_t_5 = PyTuple_GET_ITEM(sequence, 0); 
         __pyx_t_6 = PyTuple_GET_ITEM(sequence, 1); 
@@ -1637,7 +1473,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align__acgt_table(CYTHON_UNUSED PyObject *
     __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_c, __pyx_n_s_lower); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 37, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
     __pyx_t_5 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_6))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_6))) {
       __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_6);
       if (likely(__pyx_t_5)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6);
@@ -1851,7 +1687,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_2_iupac_table(CYTHON_UNUSED PyObject
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
         if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_2); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 75, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 75, __pyx_L1_error)
@@ -1859,7 +1695,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_2_iupac_table(CYTHON_UNUSED PyObject
         #endif
       } else {
         if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_3); __Pyx_INCREF(__pyx_t_2); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 75, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_1, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 75, __pyx_L1_error)
@@ -1880,7 +1716,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_2_iupac_table(CYTHON_UNUSED PyObject
     }
     if ((likely(PyTuple_CheckExact(__pyx_t_2))) || (PyList_CheckExact(__pyx_t_2))) {
       PyObject* sequence = __pyx_t_2;
-      #if !CYTHON_COMPILING_IN_PYPY
+      #if CYTHON_COMPILING_IN_CPYTHON
       Py_ssize_t size = Py_SIZE(sequence);
       #else
       Py_ssize_t size = PySequence_Size(sequence);
@@ -1890,7 +1726,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_2_iupac_table(CYTHON_UNUSED PyObject
         else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
         __PYX_ERR(0, 75, __pyx_L1_error)
       }
-      #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+      #if CYTHON_COMPILING_IN_CPYTHON
       if (likely(PyTuple_CheckExact(sequence))) {
         __pyx_t_5 = PyTuple_GET_ITEM(sequence, 0); 
         __pyx_t_6 = PyTuple_GET_ITEM(sequence, 1); 
@@ -1941,7 +1777,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_2_iupac_table(CYTHON_UNUSED PyObject
     __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_c, __pyx_n_s_lower); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 77, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
     __pyx_t_5 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_6))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_6))) {
       __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_6);
       if (likely(__pyx_t_5)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6);
@@ -2118,7 +1954,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix___init__(CYTHON_UNUSED PyO
     if (likely(!__pyx_t_5)) {
       if (likely(PyList_CheckExact(__pyx_t_4))) {
         if (__pyx_t_1 >= PyList_GET_SIZE(__pyx_t_4)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_3 = PyList_GET_ITEM(__pyx_t_4, __pyx_t_1); __Pyx_INCREF(__pyx_t_3); __pyx_t_1++; if (unlikely(0 < 0)) __PYX_ERR(0, 98, __pyx_L1_error)
         #else
         __pyx_t_3 = PySequence_ITEM(__pyx_t_4, __pyx_t_1); __pyx_t_1++; if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 98, __pyx_L1_error)
@@ -2126,7 +1962,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix___init__(CYTHON_UNUSED PyO
         #endif
       } else {
         if (__pyx_t_1 >= PyTuple_GET_SIZE(__pyx_t_4)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_3 = PyTuple_GET_ITEM(__pyx_t_4, __pyx_t_1); __Pyx_INCREF(__pyx_t_3); __pyx_t_1++; if (unlikely(0 < 0)) __PYX_ERR(0, 98, __pyx_L1_error)
         #else
         __pyx_t_3 = PySequence_ITEM(__pyx_t_4, __pyx_t_1); __pyx_t_1++; if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 98, __pyx_L1_error)
@@ -2325,17 +2161,15 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_7__str___genexpr(PyObject
   __Pyx_RefNannySetupContext("genexpr", 0);
   __pyx_cur_scope = (struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct_1_genexpr *)__pyx_tp_new_8cutadapt_6_align___pyx_scope_struct_1_genexpr(__pyx_ptype_8cutadapt_6_align___pyx_scope_struct_1_genexpr, __pyx_empty_tuple, NULL);
   if (unlikely(!__pyx_cur_scope)) {
-    __pyx_cur_scope = ((struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct_1_genexpr *)Py_None);
-    __Pyx_INCREF(Py_None);
-    __PYX_ERR(0, 112, __pyx_L1_error)
-  } else {
-    __Pyx_GOTREF(__pyx_cur_scope);
+    __Pyx_RefNannyFinishContext();
+    return NULL;
   }
+  __Pyx_GOTREF(__pyx_cur_scope);
   __pyx_cur_scope->__pyx_outer_scope = (struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct____str__ *) __pyx_self;
   __Pyx_INCREF(((PyObject *)__pyx_cur_scope->__pyx_outer_scope));
   __Pyx_GIVEREF(__pyx_cur_scope->__pyx_outer_scope);
   {
-    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___2generator, (PyObject *) __pyx_cur_scope, __pyx_n_s_genexpr, __pyx_n_s_DPMatrix___str___locals_genexpr, __pyx_n_s_cutadapt__align); if (unlikely(!gen)) __PYX_ERR(0, 112, __pyx_L1_error)
+    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___2generator, (PyObject *) __pyx_cur_scope, __pyx_n_s_genexpr, __pyx_n_s_DPMatrix___str___locals_genexpr); if (unlikely(!gen)) __PYX_ERR(0, 112, __pyx_L1_error)
     __Pyx_DECREF(__pyx_cur_scope);
     __Pyx_RefNannyFinishContext();
     return (PyObject *) gen;
@@ -2387,7 +2221,7 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___2generator(__pyx_
     if (likely(!__pyx_t_4)) {
       if (likely(PyList_CheckExact(__pyx_t_2))) {
         if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_2)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_1 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 112, __pyx_L1_error)
         #else
         __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 112, __pyx_L1_error)
@@ -2395,7 +2229,7 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___2generator(__pyx_
         #endif
       } else {
         if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_2)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 112, __pyx_L1_error)
         #else
         __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 112, __pyx_L1_error)
@@ -2443,7 +2277,6 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___2generator(__pyx_
     if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 112, __pyx_L1_error)
   }
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-  CYTHON_MAYBE_UNUSED_VAR(__pyx_cur_scope);
 
   /* function exit code */
   PyErr_SetNone(PyExc_StopIteration);
@@ -2470,17 +2303,15 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_7__str___3genexpr(PyObject
   __Pyx_RefNannySetupContext("genexpr", 0);
   __pyx_cur_scope = (struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct_2_genexpr *)__pyx_tp_new_8cutadapt_6_align___pyx_scope_struct_2_genexpr(__pyx_ptype_8cutadapt_6_align___pyx_scope_struct_2_genexpr, __pyx_empty_tuple, NULL);
   if (unlikely(!__pyx_cur_scope)) {
-    __pyx_cur_scope = ((struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct_2_genexpr *)Py_None);
-    __Pyx_INCREF(Py_None);
-    __PYX_ERR(0, 114, __pyx_L1_error)
-  } else {
-    __Pyx_GOTREF(__pyx_cur_scope);
+    __Pyx_RefNannyFinishContext();
+    return NULL;
   }
+  __Pyx_GOTREF(__pyx_cur_scope);
   __pyx_cur_scope->__pyx_outer_scope = (struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct____str__ *) __pyx_self;
   __Pyx_INCREF(((PyObject *)__pyx_cur_scope->__pyx_outer_scope));
   __Pyx_GIVEREF(__pyx_cur_scope->__pyx_outer_scope);
   {
-    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1, (PyObject *) __pyx_cur_scope, __pyx_n_s_genexpr, __pyx_n_s_DPMatrix___str___locals_genexpr, __pyx_n_s_cutadapt__align); if (unlikely(!gen)) __PYX_ERR(0, 114, __pyx_L1_error)
+    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1, (PyObject *) __pyx_cur_scope, __pyx_n_s_genexpr, __pyx_n_s_DPMatrix___str___locals_genexpr); if (unlikely(!gen)) __PYX_ERR(0, 114, __pyx_L1_error)
     __Pyx_DECREF(__pyx_cur_scope);
     __Pyx_RefNannyFinishContext();
     return (PyObject *) gen;
@@ -2533,7 +2364,7 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1(__pyx
     if (likely(!__pyx_t_3)) {
       if (likely(PyList_CheckExact(__pyx_t_1))) {
         if (__pyx_t_2 >= PyList_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_4 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_4); __pyx_t_2++; if (unlikely(0 < 0)) __PYX_ERR(0, 114, __pyx_L1_error)
         #else
         __pyx_t_4 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 114, __pyx_L1_error)
@@ -2541,7 +2372,7 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1(__pyx
         #endif
       } else {
         if (__pyx_t_2 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_4 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_4); __pyx_t_2++; if (unlikely(0 < 0)) __PYX_ERR(0, 114, __pyx_L1_error)
         #else
         __pyx_t_4 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 114, __pyx_L1_error)
@@ -2572,7 +2403,7 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1(__pyx
       __pyx_t_7 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_0_2d, __pyx_n_s_format); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 114, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_7);
       __pyx_t_8 = NULL;
-      if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_7))) {
+      if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_7))) {
         __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_7);
         if (likely(__pyx_t_8)) {
           PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_7);
@@ -2585,33 +2416,15 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1(__pyx
         __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_cur_scope->__pyx_v_v); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 114, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_6);
       } else {
-        #if CYTHON_FAST_PYCALL
-        if (PyFunction_Check(__pyx_t_7)) {
-          PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_cur_scope->__pyx_v_v};
-          __pyx_t_6 = __Pyx_PyFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 114, __pyx_L1_error)
-          __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-          __Pyx_GOTREF(__pyx_t_6);
-        } else
-        #endif
-        #if CYTHON_FAST_PYCCALL
-        if (__Pyx_PyFastCFunction_Check(__pyx_t_7)) {
-          PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_cur_scope->__pyx_v_v};
-          __pyx_t_6 = __Pyx_PyCFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 114, __pyx_L1_error)
-          __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-          __Pyx_GOTREF(__pyx_t_6);
-        } else
-        #endif
-        {
-          __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 114, __pyx_L1_error)
-          __Pyx_GOTREF(__pyx_t_9);
-          __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
-          __Pyx_INCREF(__pyx_cur_scope->__pyx_v_v);
-          __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_v);
-          PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_cur_scope->__pyx_v_v);
-          __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 114, __pyx_L1_error)
-          __Pyx_GOTREF(__pyx_t_6);
-          __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-        }
+        __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 114, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_9);
+        __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
+        __Pyx_INCREF(__pyx_cur_scope->__pyx_v_v);
+        __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_v);
+        PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_cur_scope->__pyx_v_v);
+        __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 114, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_6);
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       }
       __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
       __pyx_t_4 = __pyx_t_6;
@@ -2637,7 +2450,6 @@ static PyObject *__pyx_gb_8cutadapt_6_align_8DPMatrix_7__str___5generator1(__pyx
     if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 114, __pyx_L1_error)
   }
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-  CYTHON_MAYBE_UNUSED_VAR(__pyx_cur_scope);
 
   /* function exit code */
   PyErr_SetNone(PyExc_StopIteration);
@@ -2678,12 +2490,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_4__str__(CYTHON_UNUSED PyO
   __Pyx_RefNannySetupContext("__str__", 0);
   __pyx_cur_scope = (struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct____str__ *)__pyx_tp_new_8cutadapt_6_align___pyx_scope_struct____str__(__pyx_ptype_8cutadapt_6_align___pyx_scope_struct____str__, __pyx_empty_tuple, NULL);
   if (unlikely(!__pyx_cur_scope)) {
-    __pyx_cur_scope = ((struct __pyx_obj_8cutadapt_6_align___pyx_scope_struct____str__ *)Py_None);
-    __Pyx_INCREF(Py_None);
-    __PYX_ERR(0, 108, __pyx_L1_error)
-  } else {
-    __Pyx_GOTREF(__pyx_cur_scope);
+    __Pyx_RefNannyFinishContext();
+    return NULL;
   }
+  __Pyx_GOTREF(__pyx_cur_scope);
   __pyx_cur_scope->__pyx_v_self = __pyx_v_self;
   __Pyx_INCREF(__pyx_cur_scope->__pyx_v_self);
   __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_self);
@@ -2735,7 +2545,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_4__str__(CYTHON_UNUSED PyO
     if (likely(!__pyx_t_5)) {
       if (likely(PyList_CheckExact(__pyx_t_3))) {
         if (__pyx_t_4 >= PyList_GET_SIZE(__pyx_t_3)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyList_GET_ITEM(__pyx_t_3, __pyx_t_4); __Pyx_INCREF(__pyx_t_2); __pyx_t_4++; if (unlikely(0 < 0)) __PYX_ERR(0, 113, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_4); __pyx_t_4++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 113, __pyx_L1_error)
@@ -2743,7 +2553,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_4__str__(CYTHON_UNUSED PyO
         #endif
       } else {
         if (__pyx_t_4 >= PyTuple_GET_SIZE(__pyx_t_3)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+        #if CYTHON_COMPILING_IN_CPYTHON
         __pyx_t_2 = PyTuple_GET_ITEM(__pyx_t_3, __pyx_t_4); __Pyx_INCREF(__pyx_t_2); __pyx_t_4++; if (unlikely(0 < 0)) __PYX_ERR(0, 113, __pyx_L1_error)
         #else
         __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_4); __pyx_t_4++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 113, __pyx_L1_error)
@@ -2764,7 +2574,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_4__str__(CYTHON_UNUSED PyO
     }
     if ((likely(PyTuple_CheckExact(__pyx_t_2))) || (PyList_CheckExact(__pyx_t_2))) {
       PyObject* sequence = __pyx_t_2;
-      #if !CYTHON_COMPILING_IN_PYPY
+      #if CYTHON_COMPILING_IN_CPYTHON
       Py_ssize_t size = Py_SIZE(sequence);
       #else
       Py_ssize_t size = PySequence_Size(sequence);
@@ -2774,7 +2584,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_8DPMatrix_4__str__(CYTHON_UNUSED PyO
         else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
         __PYX_ERR(0, 113, __pyx_L1_error)
       }
-      #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+      #if CYTHON_COMPILING_IN_CPYTHON
       if (likely(PyTuple_CheckExact(sequence))) {
         __pyx_t_1 = PyTuple_GET_ITEM(sequence, 0); 
         __pyx_t_6 = PyTuple_GET_ITEM(sequence, 1); 
@@ -2900,7 +2710,7 @@ static int __pyx_pw_8cutadapt_6_align_7Aligner_1__cinit__(PyObject *__pyx_v_self
         case  1:
         if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_max_error_rate)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("__cinit__", 0, 2, 5, 1); __PYX_ERR(0, 195, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("__cinit__", 0, 2, 5, 1); __PYX_ERR(0, 200, __pyx_L3_error)
         }
         case  2:
         if (kw_args > 0) {
@@ -2919,7 +2729,7 @@ static int __pyx_pw_8cutadapt_6_align_7Aligner_1__cinit__(PyObject *__pyx_v_self
         }
       }
       if (unlikely(kw_args > 0)) {
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__cinit__") < 0)) __PYX_ERR(0, 195, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__cinit__") < 0)) __PYX_ERR(0, 200, __pyx_L3_error)
       }
     } else {
       switch (PyTuple_GET_SIZE(__pyx_args)) {
@@ -2933,32 +2743,32 @@ static int __pyx_pw_8cutadapt_6_align_7Aligner_1__cinit__(PyObject *__pyx_v_self
       }
     }
     __pyx_v_reference = ((PyObject*)values[0]);
-    __pyx_v_max_error_rate = __pyx_PyFloat_AsDouble(values[1]); if (unlikely((__pyx_v_max_error_rate == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 195, __pyx_L3_error)
+    __pyx_v_max_error_rate = __pyx_PyFloat_AsDouble(values[1]); if (unlikely((__pyx_v_max_error_rate == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 200, __pyx_L3_error)
     if (values[2]) {
-      __pyx_v_flags = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_flags == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 195, __pyx_L3_error)
+      __pyx_v_flags = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_flags == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 200, __pyx_L3_error)
     } else {
       __pyx_v_flags = ((int)15);
     }
     if (values[3]) {
-      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 195, __pyx_L3_error)
+      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 200, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_ref = ((int)0);
     }
     if (values[4]) {
-      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[4]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 195, __pyx_L3_error)
+      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[4]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 200, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_query = ((int)0);
     }
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("__cinit__", 0, 2, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 195, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("__cinit__", 0, 2, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 200, __pyx_L3_error)
   __pyx_L3_error:;
   __Pyx_AddTraceback("cutadapt._align.Aligner.__cinit__", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return -1;
   __pyx_L4_argument_unpacking_done:;
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 195, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 200, __pyx_L1_error)
   __pyx_r = __pyx_pf_8cutadapt_6_align_7Aligner___cinit__(((struct __pyx_obj_8cutadapt_6_align_Aligner *)__pyx_v_self), __pyx_v_reference, __pyx_v_max_error_rate, __pyx_v_flags, __pyx_v_wildcard_ref, __pyx_v_wildcard_query);
 
   /* function exit code */
@@ -2989,7 +2799,7 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner___cinit__(struct __pyx_obj_8cutad
   __Pyx_DECREF(__pyx_v_self->str_reference);
   __pyx_v_self->str_reference = __pyx_v_reference;
 
-  if (__Pyx_PyObject_SetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_reference, __pyx_v_reference) < 0) __PYX_ERR(0, 201, __pyx_L1_error)
+  if (__Pyx_PyObject_SetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_reference, __pyx_v_reference) < 0) __PYX_ERR(0, 206, __pyx_L1_error)
 
   __pyx_v_self->_min_overlap = 1;
 
@@ -3038,7 +2848,7 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_11min_overlap___get__(struc
   __Pyx_RefNannySetupContext("__get__", 0);
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_self->_min_overlap); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 210, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_self->_min_overlap); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 215, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_r = __pyx_t_1;
   __pyx_t_1 = 0;
@@ -3065,7 +2875,7 @@ static int __pyx_pw_8cutadapt_6_align_7Aligner_11min_overlap_3__set__(PyObject *
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("__set__ (wrapper)", 0);
   assert(__pyx_arg_value); {
-    __pyx_v_value = __Pyx_PyInt_As_int(__pyx_arg_value); if (unlikely((__pyx_v_value == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 212, __pyx_L3_error)
+    __pyx_v_value = __Pyx_PyInt_As_int(__pyx_arg_value); if (unlikely((__pyx_v_value == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 217, __pyx_L3_error)
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L3_error:;
@@ -3090,11 +2900,11 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_11min_overlap_2__set__(struct __p
   __pyx_t_1 = ((__pyx_v_value < 1) != 0);
   if (__pyx_t_1) {
 
-    __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 214, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 219, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __Pyx_Raise(__pyx_t_2, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    __PYX_ERR(0, 214, __pyx_L1_error)
+    __PYX_ERR(0, 219, __pyx_L1_error)
 
   }
 
@@ -3135,23 +2945,23 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_10indel_cost___set__(struct __pyx
   int __pyx_t_3;
   __Pyx_RefNannySetupContext("__set__", 0);
 
-  __pyx_t_1 = PyObject_RichCompare(__pyx_v_value, __pyx_int_1, Py_LT); __Pyx_XGOTREF(__pyx_t_1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 223, __pyx_L1_error)
-  __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_1); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 223, __pyx_L1_error)
+  __pyx_t_1 = PyObject_RichCompare(__pyx_v_value, __pyx_int_1, Py_LT); __Pyx_XGOTREF(__pyx_t_1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 228, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_1); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 228, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   if (__pyx_t_2) {
 
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 224, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 229, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
     __Pyx_Raise(__pyx_t_1, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-    __PYX_ERR(0, 224, __pyx_L1_error)
+    __PYX_ERR(0, 229, __pyx_L1_error)
 
   }
 
-  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_value); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 225, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_value); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 230, __pyx_L1_error)
   __pyx_v_self->_insertion_cost = __pyx_t_3;
 
-  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_value); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 226, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_value); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 231, __pyx_L1_error)
   __pyx_v_self->_deletion_cost = __pyx_t_3;
 
 
@@ -3206,7 +3016,7 @@ static int __pyx_pw_8cutadapt_6_align_7Aligner_9reference_3__set__(PyObject *__p
   int __pyx_r;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("__set__ (wrapper)", 0);
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 232, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 237, __pyx_L1_error)
   __pyx_r = __pyx_pf_8cutadapt_6_align_7Aligner_9reference_2__set__(((struct __pyx_obj_8cutadapt_6_align_Aligner *)__pyx_v_self), ((PyObject*)__pyx_v_reference));
 
   /* function exit code */
@@ -3230,40 +3040,40 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_9reference_2__set__(struct __pyx_
   PyObject *__pyx_t_6 = NULL;
   __Pyx_RefNannySetupContext("__set__", 0);
 
-  __pyx_t_1 = PyObject_Length(__pyx_v_reference); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 233, __pyx_L1_error)
+  __pyx_t_1 = PyObject_Length(__pyx_v_reference); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 238, __pyx_L1_error)
   __pyx_v_mem = ((__pyx_t_8cutadapt_6_align__Entry *)PyMem_Realloc(__pyx_v_self->column, ((__pyx_t_1 + 1) * (sizeof(__pyx_t_8cutadapt_6_align__Entry)))));
 
   __pyx_t_2 = ((!(__pyx_v_mem != 0)) != 0);
   if (__pyx_t_2) {
 
-    PyErr_NoMemory(); __PYX_ERR(0, 235, __pyx_L1_error)
+    PyErr_NoMemory(); __PYX_ERR(0, 240, __pyx_L1_error)
 
   }
 
   __pyx_v_self->column = __pyx_v_mem;
 
-  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_reference, __pyx_n_s_encode); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 237, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_reference, __pyx_n_s_encode); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 242, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
-  __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__11, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 237, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__11, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 242, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-  if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 237, __pyx_L1_error)
+  if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 242, __pyx_L1_error)
   __Pyx_GIVEREF(__pyx_t_4);
   __Pyx_GOTREF(__pyx_v_self->_reference);
   __Pyx_DECREF(__pyx_v_self->_reference);
   __pyx_v_self->_reference = ((PyObject*)__pyx_t_4);
   __pyx_t_4 = 0;
 
-  __pyx_t_1 = PyObject_Length(__pyx_v_reference); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 238, __pyx_L1_error)
+  __pyx_t_1 = PyObject_Length(__pyx_v_reference); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 243, __pyx_L1_error)
   __pyx_v_self->m = __pyx_t_1;
 
   __pyx_t_2 = (__pyx_v_self->wildcard_ref != 0);
   if (__pyx_t_2) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_reference, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 240, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_reference, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 245, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_5 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_5)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -3273,39 +3083,21 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_9reference_2__set__(struct __pyx_
       }
     }
     if (!__pyx_t_5) {
-      __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 240, __pyx_L1_error)
+      __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 245, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_4);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_5, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_4 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 240, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-        __Pyx_GOTREF(__pyx_t_4);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_5, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_4 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 240, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-        __Pyx_GOTREF(__pyx_t_4);
-      } else
-      #endif
-      {
-        __pyx_t_6 = PyTuple_New(1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 240, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_GIVEREF(__pyx_t_5); PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_5); __pyx_t_5 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_6, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_6, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 240, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_4);
-        __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-      }
+      __pyx_t_6 = PyTuple_New(1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 245, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_6);
+      __Pyx_GIVEREF(__pyx_t_5); PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_5); __pyx_t_5 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_6, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_6, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 245, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_4);
+      __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 240, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 245, __pyx_L1_error)
     __Pyx_GIVEREF(__pyx_t_4);
     __Pyx_GOTREF(__pyx_v_self->_reference);
     __Pyx_DECREF(__pyx_v_self->_reference);
@@ -3318,10 +3110,10 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_9reference_2__set__(struct __pyx_
   __pyx_t_2 = (__pyx_v_self->wildcard_query != 0);
   if (__pyx_t_2) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_reference, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 242, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_reference, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 247, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_6 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_6)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -3331,39 +3123,21 @@ static int __pyx_pf_8cutadapt_6_align_7Aligner_9reference_2__set__(struct __pyx_
       }
     }
     if (!__pyx_t_6) {
-      __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 242, __pyx_L1_error)
+      __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 247, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_4);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_4 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 242, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_4);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_4 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 242, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_4);
-      } else
-      #endif
-      {
-        __pyx_t_5 = PyTuple_New(1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 242, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_5);
-        __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_6); __pyx_t_6 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_5, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 242, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_4);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      }
+      __pyx_t_5 = PyTuple_New(1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 247, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_6); __pyx_t_6 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_5, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 247, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_4);
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 242, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_4))||((__pyx_t_4) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_4)->tp_name), 0))) __PYX_ERR(0, 247, __pyx_L1_error)
     __Pyx_GIVEREF(__pyx_t_4);
     __Pyx_GOTREF(__pyx_v_self->_reference);
     __Pyx_DECREF(__pyx_v_self->_reference);
@@ -3465,7 +3239,7 @@ static PyObject *__pyx_pw_8cutadapt_6_align_7Aligner_5locate(PyObject *__pyx_v_s
   PyObject *__pyx_r = 0;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("locate (wrapper)", 0);
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 260, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 265, __pyx_L1_error)
   __pyx_r = __pyx_pf_8cutadapt_6_align_7Aligner_4locate(((struct __pyx_obj_8cutadapt_6_align_Aligner *)__pyx_v_self), ((PyObject*)__pyx_v_query));
 
   /* function exit code */
@@ -3533,25 +3307,25 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
   PyObject *__pyx_t_20 = NULL;
   __Pyx_RefNannySetupContext("locate", 0);
 
-  __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_self->_reference); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 274, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_self->_reference); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 279, __pyx_L1_error)
   __pyx_v_s1 = __pyx_t_1;
 
-  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query, __pyx_n_s_encode); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 275, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query, __pyx_n_s_encode); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 280, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__12, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 275, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__12, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 280, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-  if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 275, __pyx_L1_error)
+  if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 280, __pyx_L1_error)
   __pyx_v_query_bytes = ((PyObject*)__pyx_t_3);
   __pyx_t_3 = 0;
 
-  __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 276, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 281, __pyx_L1_error)
   __pyx_v_s2 = __pyx_t_1;
 
   __pyx_t_4 = __pyx_v_self->m;
   __pyx_v_m = __pyx_t_4;
 
-  __pyx_t_5 = PyObject_Length(__pyx_v_query); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 278, __pyx_L1_error)
+  __pyx_t_5 = PyObject_Length(__pyx_v_query); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 283, __pyx_L1_error)
   __pyx_v_n = __pyx_t_5;
 
   __pyx_t_6 = __pyx_v_self->column;
@@ -3571,10 +3345,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
   __pyx_t_8 = (__pyx_v_self->wildcard_query != 0);
   if (__pyx_t_8) {
 
-    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 287, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 292, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __pyx_t_9 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_2);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
@@ -3584,43 +3358,25 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
       }
     }
     if (!__pyx_t_9) {
-      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 287, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 292, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_3);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 287, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 287, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-      } else
-      #endif
-      {
-        __pyx_t_10 = PyTuple_New(1+1); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 287, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_10);
-        __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_10, 0, __pyx_t_9); __pyx_t_9 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_10, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_10, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 287, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
-      }
+      __pyx_t_10 = PyTuple_New(1+1); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 292, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_10);
+      __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_10, 0, __pyx_t_9); __pyx_t_9 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_10, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_10, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 292, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_3);
+      __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
     }
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 287, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 292, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_query_bytes, ((PyObject*)__pyx_t_3));
     __pyx_t_3 = 0;
 
-    __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 288, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 293, __pyx_L1_error)
     __pyx_v_s2 = __pyx_t_1;
 
     goto __pyx_L3;
@@ -3629,10 +3385,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
   __pyx_t_8 = (__pyx_v_self->wildcard_ref != 0);
   if (__pyx_t_8) {
 
-    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 290, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 295, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __pyx_t_10 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
       __pyx_t_10 = PyMethod_GET_SELF(__pyx_t_2);
       if (likely(__pyx_t_10)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
@@ -3642,43 +3398,25 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
       }
     }
     if (!__pyx_t_10) {
-      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 290, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 295, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_3);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_10, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 290, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_10); __pyx_t_10 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_10, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 290, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_10); __pyx_t_10 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-      } else
-      #endif
-      {
-        __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 290, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_9);
-        __Pyx_GIVEREF(__pyx_t_10); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_10); __pyx_t_10 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_9, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 290, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      }
+      __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 295, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_9);
+      __Pyx_GIVEREF(__pyx_t_10); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_10); __pyx_t_10 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_9, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 295, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_3);
+      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 290, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 295, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_query_bytes, ((PyObject*)__pyx_t_3));
     __pyx_t_3 = 0;
 
-    __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 291, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 296, __pyx_L1_error)
     __pyx_v_s2 = __pyx_t_1;
 
   }
@@ -3849,52 +3587,34 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
   __pyx_t_8 = (__pyx_v_self->debug != 0);
   if (__pyx_t_8) {
 
-    __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_DPMatrix); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 349, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_DPMatrix); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 354, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
     __pyx_t_9 = NULL;
-    __pyx_t_13 = 0;
-    if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) {
+    __pyx_t_5 = 0;
+    if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_2))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_2);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
         __Pyx_INCREF(__pyx_t_9);
         __Pyx_INCREF(function);
         __Pyx_DECREF_SET(__pyx_t_2, function);
-        __pyx_t_13 = 1;
-      }
-    }
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_2)) {
-      PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_v_self->str_reference, __pyx_v_query};
-      __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_13, 2+__pyx_t_13); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 349, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-      __Pyx_GOTREF(__pyx_t_3);
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-      PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_v_self->str_reference, __pyx_v_query};
-      __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_13, 2+__pyx_t_13); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 349, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-      __Pyx_GOTREF(__pyx_t_3);
-    } else
-    #endif
-    {
-      __pyx_t_10 = PyTuple_New(2+__pyx_t_13); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 349, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_10);
-      if (__pyx_t_9) {
-        __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_10, 0, __pyx_t_9); __pyx_t_9 = NULL;
+        __pyx_t_5 = 1;
       }
-      __Pyx_INCREF(__pyx_v_self->str_reference);
-      __Pyx_GIVEREF(__pyx_v_self->str_reference);
-      PyTuple_SET_ITEM(__pyx_t_10, 0+__pyx_t_13, __pyx_v_self->str_reference);
-      __Pyx_INCREF(__pyx_v_query);
-      __Pyx_GIVEREF(__pyx_v_query);
-      PyTuple_SET_ITEM(__pyx_t_10, 1+__pyx_t_13, __pyx_v_query);
-      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_10, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 349, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_3);
-      __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
     }
+    __pyx_t_10 = PyTuple_New(2+__pyx_t_5); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 354, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_10);
+    if (__pyx_t_9) {
+      __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_10, 0, __pyx_t_9); __pyx_t_9 = NULL;
+    }
+    __Pyx_INCREF(__pyx_v_self->str_reference);
+    __Pyx_GIVEREF(__pyx_v_self->str_reference);
+    PyTuple_SET_ITEM(__pyx_t_10, 0+__pyx_t_5, __pyx_v_self->str_reference);
+    __Pyx_INCREF(__pyx_v_query);
+    __Pyx_GIVEREF(__pyx_v_query);
+    PyTuple_SET_ITEM(__pyx_t_10, 1+__pyx_t_5, __pyx_v_query);
+    __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_10, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 354, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_3);
+    __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
     __Pyx_GIVEREF(__pyx_t_3);
     __Pyx_GOTREF(__pyx_v_self->_dpmatrix);
@@ -3906,67 +3626,43 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
     for (__pyx_t_13 = 0; __pyx_t_13 < __pyx_t_15; __pyx_t_13+=1) {
       __pyx_v_i = __pyx_t_13;
 
-      __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_dpmatrix, __pyx_n_s_set_entry); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 351, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_dpmatrix, __pyx_n_s_set_entry); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 356, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
-      __pyx_t_10 = __Pyx_PyInt_From_int(__pyx_v_i); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 351, __pyx_L1_error)
+      __pyx_t_10 = __Pyx_PyInt_From_int(__pyx_v_i); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 356, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_10);
-      __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_min_n); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 351, __pyx_L1_error)
+      __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_min_n); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 356, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_9);
-      __pyx_t_18 = __Pyx_PyInt_From_int((__pyx_v_column[__pyx_v_i]).cost); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 351, __pyx_L1_error)
+      __pyx_t_18 = __Pyx_PyInt_From_int((__pyx_v_column[__pyx_v_i]).cost); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 356, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_18);
       __pyx_t_19 = NULL;
-      __pyx_t_12 = 0;
-      if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) {
+      __pyx_t_5 = 0;
+      if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
         __pyx_t_19 = PyMethod_GET_SELF(__pyx_t_2);
         if (likely(__pyx_t_19)) {
           PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
           __Pyx_INCREF(__pyx_t_19);
           __Pyx_INCREF(function);
           __Pyx_DECREF_SET(__pyx_t_2, function);
-          __pyx_t_12 = 1;
+          __pyx_t_5 = 1;
         }
       }
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[4] = {__pyx_t_19, __pyx_t_10, __pyx_t_9, __pyx_t_18};
-        __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_12, 3+__pyx_t_12); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 351, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_19); __pyx_t_19 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_DECREF(__pyx_t_18); __pyx_t_18 = 0;
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-        PyObject *__pyx_temp[4] = {__pyx_t_19, __pyx_t_10, __pyx_t_9, __pyx_t_18};
-        __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_12, 3+__pyx_t_12); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 351, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_19); __pyx_t_19 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_DECREF(__pyx_t_18); __pyx_t_18 = 0;
-      } else
-      #endif
-      {
-        __pyx_t_20 = PyTuple_New(3+__pyx_t_12); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 351, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_20);
-        if (__pyx_t_19) {
-          __Pyx_GIVEREF(__pyx_t_19); PyTuple_SET_ITEM(__pyx_t_20, 0, __pyx_t_19); __pyx_t_19 = NULL;
-        }
-        __Pyx_GIVEREF(__pyx_t_10);
-        PyTuple_SET_ITEM(__pyx_t_20, 0+__pyx_t_12, __pyx_t_10);
-        __Pyx_GIVEREF(__pyx_t_9);
-        PyTuple_SET_ITEM(__pyx_t_20, 1+__pyx_t_12, __pyx_t_9);
-        __Pyx_GIVEREF(__pyx_t_18);
-        PyTuple_SET_ITEM(__pyx_t_20, 2+__pyx_t_12, __pyx_t_18);
-        __pyx_t_10 = 0;
-        __pyx_t_9 = 0;
-        __pyx_t_18 = 0;
-        __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_20, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 351, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_20); __pyx_t_20 = 0;
+      __pyx_t_20 = PyTuple_New(3+__pyx_t_5); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 356, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_20);
+      if (__pyx_t_19) {
+        __Pyx_GIVEREF(__pyx_t_19); PyTuple_SET_ITEM(__pyx_t_20, 0, __pyx_t_19); __pyx_t_19 = NULL;
       }
+      __Pyx_GIVEREF(__pyx_t_10);
+      PyTuple_SET_ITEM(__pyx_t_20, 0+__pyx_t_5, __pyx_t_10);
+      __Pyx_GIVEREF(__pyx_t_9);
+      PyTuple_SET_ITEM(__pyx_t_20, 1+__pyx_t_5, __pyx_t_9);
+      __Pyx_GIVEREF(__pyx_t_18);
+      PyTuple_SET_ITEM(__pyx_t_20, 2+__pyx_t_5, __pyx_t_18);
+      __pyx_t_10 = 0;
+      __pyx_t_9 = 0;
+      __pyx_t_18 = 0;
+      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_20, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 356, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_3);
+      __Pyx_DECREF(__pyx_t_20); __pyx_t_20 = 0;
       __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
       __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
     }
@@ -4134,67 +3830,43 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
                   for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_15; __pyx_t_12+=1) {
                     __pyx_v_i = __pyx_t_12;
 
-                    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_dpmatrix, __pyx_n_s_set_entry); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 424, __pyx_L44_error)
+                    __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_self->_dpmatrix, __pyx_n_s_set_entry); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 429, __pyx_L44_error)
                     __Pyx_GOTREF(__pyx_t_2);
-                    __pyx_t_20 = __Pyx_PyInt_From_int(__pyx_v_i); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 424, __pyx_L44_error)
+                    __pyx_t_20 = __Pyx_PyInt_From_int(__pyx_v_i); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 429, __pyx_L44_error)
                     __Pyx_GOTREF(__pyx_t_20);
-                    __pyx_t_18 = __Pyx_PyInt_From_int(__pyx_v_j); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 424, __pyx_L44_error)
+                    __pyx_t_18 = __Pyx_PyInt_From_int(__pyx_v_j); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 429, __pyx_L44_error)
                     __Pyx_GOTREF(__pyx_t_18);
-                    __pyx_t_9 = __Pyx_PyInt_From_int((__pyx_v_column[__pyx_v_i]).cost); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 424, __pyx_L44_error)
+                    __pyx_t_9 = __Pyx_PyInt_From_int((__pyx_v_column[__pyx_v_i]).cost); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 429, __pyx_L44_error)
                     __Pyx_GOTREF(__pyx_t_9);
                     __pyx_t_10 = NULL;
-                    __pyx_t_16 = 0;
-                    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) {
+                    __pyx_t_5 = 0;
+                    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
                       __pyx_t_10 = PyMethod_GET_SELF(__pyx_t_2);
                       if (likely(__pyx_t_10)) {
                         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
                         __Pyx_INCREF(__pyx_t_10);
                         __Pyx_INCREF(function);
                         __Pyx_DECREF_SET(__pyx_t_2, function);
-                        __pyx_t_16 = 1;
+                        __pyx_t_5 = 1;
                       }
                     }
-                    #if CYTHON_FAST_PYCALL
-                    if (PyFunction_Check(__pyx_t_2)) {
-                      PyObject *__pyx_temp[4] = {__pyx_t_10, __pyx_t_20, __pyx_t_18, __pyx_t_9};
-                      __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_16, 3+__pyx_t_16); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 424, __pyx_L44_error)
-                      __Pyx_XDECREF(__pyx_t_10); __pyx_t_10 = 0;
-                      __Pyx_GOTREF(__pyx_t_3);
-                      __Pyx_DECREF(__pyx_t_20); __pyx_t_20 = 0;
-                      __Pyx_DECREF(__pyx_t_18); __pyx_t_18 = 0;
-                      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-                    } else
-                    #endif
-                    #if CYTHON_FAST_PYCCALL
-                    if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-                      PyObject *__pyx_temp[4] = {__pyx_t_10, __pyx_t_20, __pyx_t_18, __pyx_t_9};
-                      __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_16, 3+__pyx_t_16); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 424, __pyx_L44_error)
-                      __Pyx_XDECREF(__pyx_t_10); __pyx_t_10 = 0;
-                      __Pyx_GOTREF(__pyx_t_3);
-                      __Pyx_DECREF(__pyx_t_20); __pyx_t_20 = 0;
-                      __Pyx_DECREF(__pyx_t_18); __pyx_t_18 = 0;
-                      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-                    } else
-                    #endif
-                    {
-                      __pyx_t_19 = PyTuple_New(3+__pyx_t_16); if (unlikely(!__pyx_t_19)) __PYX_ERR(0, 424, __pyx_L44_error)
-                      __Pyx_GOTREF(__pyx_t_19);
-                      if (__pyx_t_10) {
-                        __Pyx_GIVEREF(__pyx_t_10); PyTuple_SET_ITEM(__pyx_t_19, 0, __pyx_t_10); __pyx_t_10 = NULL;
-                      }
-                      __Pyx_GIVEREF(__pyx_t_20);
-                      PyTuple_SET_ITEM(__pyx_t_19, 0+__pyx_t_16, __pyx_t_20);
-                      __Pyx_GIVEREF(__pyx_t_18);
-                      PyTuple_SET_ITEM(__pyx_t_19, 1+__pyx_t_16, __pyx_t_18);
-                      __Pyx_GIVEREF(__pyx_t_9);
-                      PyTuple_SET_ITEM(__pyx_t_19, 2+__pyx_t_16, __pyx_t_9);
-                      __pyx_t_20 = 0;
-                      __pyx_t_18 = 0;
-                      __pyx_t_9 = 0;
-                      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_19, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 424, __pyx_L44_error)
-                      __Pyx_GOTREF(__pyx_t_3);
-                      __Pyx_DECREF(__pyx_t_19); __pyx_t_19 = 0;
+                    __pyx_t_19 = PyTuple_New(3+__pyx_t_5); if (unlikely(!__pyx_t_19)) __PYX_ERR(0, 429, __pyx_L44_error)
+                    __Pyx_GOTREF(__pyx_t_19);
+                    if (__pyx_t_10) {
+                      __Pyx_GIVEREF(__pyx_t_10); PyTuple_SET_ITEM(__pyx_t_19, 0, __pyx_t_10); __pyx_t_10 = NULL;
                     }
+                    __Pyx_GIVEREF(__pyx_t_20);
+                    PyTuple_SET_ITEM(__pyx_t_19, 0+__pyx_t_5, __pyx_t_20);
+                    __Pyx_GIVEREF(__pyx_t_18);
+                    PyTuple_SET_ITEM(__pyx_t_19, 1+__pyx_t_5, __pyx_t_18);
+                    __Pyx_GIVEREF(__pyx_t_9);
+                    PyTuple_SET_ITEM(__pyx_t_19, 2+__pyx_t_5, __pyx_t_9);
+                    __pyx_t_20 = 0;
+                    __pyx_t_18 = 0;
+                    __pyx_t_9 = 0;
+                    __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_19, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 429, __pyx_L44_error)
+                    __Pyx_GOTREF(__pyx_t_3);
+                    __Pyx_DECREF(__pyx_t_19); __pyx_t_19 = 0;
                     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
                     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
                   }
@@ -4446,25 +4118,25 @@ static PyObject *__pyx_pf_8cutadapt_6_align_7Aligner_4locate(struct __pyx_obj_8c
   if (unlikely(!Py_OptimizeFlag)) {
     if (unlikely(!(((__pyx_v_best.ref_stop - __pyx_v_start1) > 0) != 0))) {
       PyErr_SetNone(PyExc_AssertionError);
-      __PYX_ERR(0, 477, __pyx_L1_error)
+      __PYX_ERR(0, 482, __pyx_L1_error)
     }
   }
   #endif
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_start1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_start1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
-  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_best.ref_stop); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_best.ref_stop); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_19 = __Pyx_PyInt_From_int(__pyx_v_start2); if (unlikely(!__pyx_t_19)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_19 = __Pyx_PyInt_From_int(__pyx_v_start2); if (unlikely(!__pyx_t_19)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_19);
-  __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_best.query_stop); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_9 = __Pyx_PyInt_From_int(__pyx_v_best.query_stop); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  __pyx_t_18 = __Pyx_PyInt_From_int(__pyx_v_best.matches); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_18 = __Pyx_PyInt_From_int(__pyx_v_best.matches); if (unlikely(!__pyx_t_18)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_18);
-  __pyx_t_20 = __Pyx_PyInt_From_int(__pyx_v_best.cost); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_20 = __Pyx_PyInt_From_int(__pyx_v_best.cost); if (unlikely(!__pyx_t_20)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_20);
-  __pyx_t_10 = PyTuple_New(6); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 478, __pyx_L1_error)
+  __pyx_t_10 = PyTuple_New(6); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 483, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_10);
   __Pyx_GIVEREF(__pyx_t_3);
   PyTuple_SET_ITEM(__pyx_t_10, 0, __pyx_t_3);
@@ -4570,12 +4242,12 @@ static PyObject *__pyx_pw_8cutadapt_6_align_5locate(PyObject *__pyx_self, PyObje
         case  1:
         if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_query)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, 1); __PYX_ERR(0, 484, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, 1); __PYX_ERR(0, 489, __pyx_L3_error)
         }
         case  2:
         if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_max_error_rate)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, 2); __PYX_ERR(0, 484, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, 2); __PYX_ERR(0, 489, __pyx_L3_error)
         }
         case  3:
         if (kw_args > 0) {
@@ -4599,7 +4271,7 @@ static PyObject *__pyx_pw_8cutadapt_6_align_5locate(PyObject *__pyx_self, PyObje
         }
       }
       if (unlikely(kw_args > 0)) {
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "locate") < 0)) __PYX_ERR(0, 484, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "locate") < 0)) __PYX_ERR(0, 489, __pyx_L3_error)
       }
     } else {
       switch (PyTuple_GET_SIZE(__pyx_args)) {
@@ -4616,38 +4288,38 @@ static PyObject *__pyx_pw_8cutadapt_6_align_5locate(PyObject *__pyx_self, PyObje
     }
     __pyx_v_reference = ((PyObject*)values[0]);
     __pyx_v_query = ((PyObject*)values[1]);
-    __pyx_v_max_error_rate = __pyx_PyFloat_AsDouble(values[2]); if (unlikely((__pyx_v_max_error_rate == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 484, __pyx_L3_error)
+    __pyx_v_max_error_rate = __pyx_PyFloat_AsDouble(values[2]); if (unlikely((__pyx_v_max_error_rate == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 489, __pyx_L3_error)
     if (values[3]) {
-      __pyx_v_flags = __Pyx_PyInt_As_int(values[3]); if (unlikely((__pyx_v_flags == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 484, __pyx_L3_error)
+      __pyx_v_flags = __Pyx_PyInt_As_int(values[3]); if (unlikely((__pyx_v_flags == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 489, __pyx_L3_error)
     } else {
       __pyx_v_flags = ((int)15);
     }
     if (values[4]) {
-      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[4]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 484, __pyx_L3_error)
+      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[4]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 489, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_ref = ((int)0);
     }
     if (values[5]) {
-      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[5]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 484, __pyx_L3_error)
+      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[5]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 489, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_query = ((int)0);
     }
     if (values[6]) {
-      __pyx_v_min_overlap = __Pyx_PyInt_As_int(values[6]); if (unlikely((__pyx_v_min_overlap == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 484, __pyx_L3_error)
+      __pyx_v_min_overlap = __Pyx_PyInt_As_int(values[6]); if (unlikely((__pyx_v_min_overlap == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 489, __pyx_L3_error)
     } else {
       __pyx_v_min_overlap = ((int)1);
     }
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 484, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("locate", 0, 3, 7, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 489, __pyx_L3_error)
   __pyx_L3_error:;
   __Pyx_AddTraceback("cutadapt._align.locate", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return NULL;
   __pyx_L4_argument_unpacking_done:;
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 484, __pyx_L1_error)
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 484, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_reference), (&PyString_Type), 1, "reference", 1))) __PYX_ERR(0, 489, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 489, __pyx_L1_error)
   __pyx_r = __pyx_pf_8cutadapt_6_align_4locate(__pyx_self, __pyx_v_reference, __pyx_v_query, __pyx_v_max_error_rate, __pyx_v_flags, __pyx_v_wildcard_ref, __pyx_v_wildcard_query, __pyx_v_min_overlap);
 
   /* function exit code */
@@ -4670,15 +4342,15 @@ static PyObject *__pyx_pf_8cutadapt_6_align_4locate(CYTHON_UNUSED PyObject *__py
   PyObject *__pyx_t_5 = NULL;
   __Pyx_RefNannySetupContext("locate", 0);
 
-  __pyx_t_1 = PyFloat_FromDouble(__pyx_v_max_error_rate); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_1 = PyFloat_FromDouble(__pyx_v_max_error_rate); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_flags); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_flags); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_3 = __Pyx_PyBool_FromLong(__pyx_v_wildcard_ref); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyBool_FromLong(__pyx_v_wildcard_ref); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
-  __pyx_t_4 = __Pyx_PyBool_FromLong(__pyx_v_wildcard_query); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyBool_FromLong(__pyx_v_wildcard_query); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
-  __pyx_t_5 = PyTuple_New(5); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_5 = PyTuple_New(5); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __Pyx_INCREF(__pyx_v_reference);
   __Pyx_GIVEREF(__pyx_v_reference);
@@ -4695,22 +4367,22 @@ static PyObject *__pyx_pf_8cutadapt_6_align_4locate(CYTHON_UNUSED PyObject *__py
   __pyx_t_2 = 0;
   __pyx_t_3 = 0;
   __pyx_t_4 = 0;
-  __pyx_t_4 = __Pyx_PyObject_Call(((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner), __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 485, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyObject_Call(((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner), __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 490, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __pyx_v_aligner = ((struct __pyx_obj_8cutadapt_6_align_Aligner *)__pyx_t_4);
   __pyx_t_4 = 0;
 
-  __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_min_overlap); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 486, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_min_overlap); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 491, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
-  if (__Pyx_PyObject_SetAttrStr(((PyObject *)__pyx_v_aligner), __pyx_n_s_min_overlap, __pyx_t_4) < 0) __PYX_ERR(0, 486, __pyx_L1_error)
+  if (__Pyx_PyObject_SetAttrStr(((PyObject *)__pyx_v_aligner), __pyx_n_s_min_overlap, __pyx_t_4) < 0) __PYX_ERR(0, 491, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_aligner), __pyx_n_s_locate); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 487, __pyx_L1_error)
+  __pyx_t_5 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_aligner), __pyx_n_s_locate); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 492, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __pyx_t_3 = NULL;
-  if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_5))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_5))) {
     __pyx_t_3 = PyMethod_GET_SELF(__pyx_t_5);
     if (likely(__pyx_t_3)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
@@ -4720,36 +4392,18 @@ static PyObject *__pyx_pf_8cutadapt_6_align_4locate(CYTHON_UNUSED PyObject *__py
     }
   }
   if (!__pyx_t_3) {
-    __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_5, __pyx_v_query); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 487, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_t_5, __pyx_v_query); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 492, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
   } else {
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_5)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_3, __pyx_v_query};
-      __pyx_t_4 = __Pyx_PyFunction_FastCall(__pyx_t_5, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 487, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0;
-      __Pyx_GOTREF(__pyx_t_4);
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_5)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_3, __pyx_v_query};
-      __pyx_t_4 = __Pyx_PyCFunction_FastCall(__pyx_t_5, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 487, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0;
-      __Pyx_GOTREF(__pyx_t_4);
-    } else
-    #endif
-    {
-      __pyx_t_2 = PyTuple_New(1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 487, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_2);
-      __Pyx_GIVEREF(__pyx_t_3); PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_3); __pyx_t_3 = NULL;
-      __Pyx_INCREF(__pyx_v_query);
-      __Pyx_GIVEREF(__pyx_v_query);
-      PyTuple_SET_ITEM(__pyx_t_2, 0+1, __pyx_v_query);
-      __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_2, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 487, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    }
+    __pyx_t_2 = PyTuple_New(1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 492, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_2);
+    __Pyx_GIVEREF(__pyx_t_3); PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_3); __pyx_t_3 = NULL;
+    __Pyx_INCREF(__pyx_v_query);
+    __Pyx_GIVEREF(__pyx_v_query);
+    PyTuple_SET_ITEM(__pyx_t_2, 0+1, __pyx_v_query);
+    __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_2, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 492, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_4);
+    __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   }
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __pyx_r = __pyx_t_4;
@@ -4808,7 +4462,7 @@ static PyObject *__pyx_pw_8cutadapt_6_align_7compare_prefixes(PyObject *__pyx_se
         case  1:
         if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_query)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("compare_prefixes", 0, 2, 4, 1); __PYX_ERR(0, 490, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("compare_prefixes", 0, 2, 4, 1); __PYX_ERR(0, 495, __pyx_L3_error)
         }
         case  2:
         if (kw_args > 0) {
@@ -4822,7 +4476,7 @@ static PyObject *__pyx_pw_8cutadapt_6_align_7compare_prefixes(PyObject *__pyx_se
         }
       }
       if (unlikely(kw_args > 0)) {
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "compare_prefixes") < 0)) __PYX_ERR(0, 490, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "compare_prefixes") < 0)) __PYX_ERR(0, 495, __pyx_L3_error)
       }
     } else {
       switch (PyTuple_GET_SIZE(__pyx_args)) {
@@ -4837,26 +4491,26 @@ static PyObject *__pyx_pw_8cutadapt_6_align_7compare_prefixes(PyObject *__pyx_se
     __pyx_v_ref = ((PyObject*)values[0]);
     __pyx_v_query = ((PyObject*)values[1]);
     if (values[2]) {
-      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[2]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 490, __pyx_L3_error)
+      __pyx_v_wildcard_ref = __Pyx_PyObject_IsTrue(values[2]); if (unlikely((__pyx_v_wildcard_ref == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 495, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_ref = ((int)0);
     }
     if (values[3]) {
-      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 490, __pyx_L3_error)
+      __pyx_v_wildcard_query = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_wildcard_query == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 495, __pyx_L3_error)
     } else {
       __pyx_v_wildcard_query = ((int)0);
     }
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("compare_prefixes", 0, 2, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 490, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("compare_prefixes", 0, 2, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 495, __pyx_L3_error)
   __pyx_L3_error:;
   __Pyx_AddTraceback("cutadapt._align.compare_prefixes", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
   return NULL;
   __pyx_L4_argument_unpacking_done:;
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_ref), (&PyString_Type), 1, "ref", 1))) __PYX_ERR(0, 490, __pyx_L1_error)
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 490, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_ref), (&PyString_Type), 1, "ref", 1))) __PYX_ERR(0, 495, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_query), (&PyString_Type), 1, "query", 1))) __PYX_ERR(0, 495, __pyx_L1_error)
   __pyx_r = __pyx_pf_8cutadapt_6_align_6compare_prefixes(__pyx_self, __pyx_v_ref, __pyx_v_query, __pyx_v_wildcard_ref, __pyx_v_wildcard_query);
 
   /* function exit code */
@@ -4894,27 +4548,27 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   PyObject *__pyx_t_11 = NULL;
   __Pyx_RefNannySetupContext("compare_prefixes", 0);
 
-  __pyx_t_1 = PyObject_Length(__pyx_v_ref); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 500, __pyx_L1_error)
+  __pyx_t_1 = PyObject_Length(__pyx_v_ref); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 505, __pyx_L1_error)
   __pyx_v_m = __pyx_t_1;
 
-  __pyx_t_1 = PyObject_Length(__pyx_v_query); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 501, __pyx_L1_error)
+  __pyx_t_1 = PyObject_Length(__pyx_v_query); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 506, __pyx_L1_error)
   __pyx_v_n = __pyx_t_1;
 
-  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query, __pyx_n_s_encode); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 502, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_query, __pyx_n_s_encode); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 507, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__13, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 502, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__13, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 507, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-  if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 502, __pyx_L1_error)
+  if (!(likely(PyBytes_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(0, 507, __pyx_L1_error)
   __pyx_v_query_bytes = ((PyObject*)__pyx_t_3);
   __pyx_t_3 = 0;
 
-  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref, __pyx_n_s_encode); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 503, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref, __pyx_n_s_encode); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 508, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
-  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__14, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 503, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_tuple__14, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 508, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-  if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 503, __pyx_L1_error)
+  if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 508, __pyx_L1_error)
   __pyx_v_ref_bytes = ((PyObject*)__pyx_t_2);
   __pyx_t_2 = 0;
 
@@ -4934,10 +4588,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   __pyx_t_7 = (__pyx_v_wildcard_ref != 0);
   if (__pyx_t_7) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 511, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 516, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_8 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_8)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -4947,39 +4601,21 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
       }
     }
     if (!__pyx_t_8) {
-      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 511, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 516, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_2 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 511, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_2 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 511, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      {
-        __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 511, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_9);
-        __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 511, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      }
+      __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 516, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_9);
+      __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 516, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_2);
+      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 511, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 516, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_ref_bytes, ((PyObject*)__pyx_t_2));
     __pyx_t_2 = 0;
 
@@ -4989,10 +4625,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   __pyx_t_7 = (__pyx_v_wildcard_query != 0);
   if (__pyx_t_7) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 513, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_ref_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 518, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_9 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -5002,39 +4638,21 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
       }
     }
     if (!__pyx_t_9) {
-      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 513, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 518, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_2 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 513, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_2 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 513, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      {
-        __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 513, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_9); __pyx_t_9 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_8, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 513, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      }
+      __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 518, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_9); __pyx_t_9 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_8, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 518, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_2);
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 513, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 518, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_ref_bytes, ((PyObject*)__pyx_t_2));
     __pyx_t_2 = 0;
 
@@ -5049,10 +4667,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   __pyx_t_7 = (__pyx_v_wildcard_query != 0);
   if (__pyx_t_7) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 517, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 522, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_8 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_8)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -5062,39 +4680,21 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
       }
     }
     if (!__pyx_t_8) {
-      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 517, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_IUPAC_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 522, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_2 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 517, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_8cutadapt_6_align_IUPAC_TABLE};
-        __pyx_t_2 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 517, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      {
-        __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 517, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_9);
-        __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
-        __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 517, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      }
+      __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 522, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_9);
+      __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_8cutadapt_6_align_IUPAC_TABLE);
+      __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_9, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 522, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_2);
+      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 517, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 522, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_query_bytes, ((PyObject*)__pyx_t_2));
     __pyx_t_2 = 0;
 
@@ -5104,10 +4704,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   __pyx_t_7 = (__pyx_v_wildcard_ref != 0);
   if (__pyx_t_7) {
 
-    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 519, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_v_query_bytes, __pyx_n_s_translate); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 524, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __pyx_t_9 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_3);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -5117,39 +4717,21 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
       }
     }
     if (!__pyx_t_9) {
-      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 519, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_8cutadapt_6_align_ACGT_TABLE); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 524, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_2 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 519, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_9, __pyx_v_8cutadapt_6_align_ACGT_TABLE};
-        __pyx_t_2 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 519, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-      } else
-      #endif
-      {
-        __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 519, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_9); __pyx_t_9 = NULL;
-        __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
-        __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_8, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 519, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      }
+      __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 524, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_9); __pyx_t_9 = NULL;
+      __Pyx_INCREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __Pyx_GIVEREF(__pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_8cutadapt_6_align_ACGT_TABLE);
+      __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_8, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 524, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_2);
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     }
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 519, __pyx_L1_error)
+    if (!(likely(PyBytes_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "bytes", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 524, __pyx_L1_error)
     __Pyx_DECREF_SET(__pyx_v_query_bytes, ((PyObject*)__pyx_t_2));
     __pyx_t_2 = 0;
 
@@ -5163,14 +4745,14 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
     for (__pyx_t_4 = 0; __pyx_t_4 < __pyx_t_6; __pyx_t_4+=1) {
       __pyx_v_i = __pyx_t_4;
 
-      __pyx_t_2 = __Pyx_GetItemInt(__pyx_v_ref, __pyx_v_i, int, 1, __Pyx_PyInt_From_int, 0, 1, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 523, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_GetItemInt(__pyx_v_ref, __pyx_v_i, int, 1, __Pyx_PyInt_From_int, 0, 1, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 528, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_2);
-      __pyx_t_3 = __Pyx_GetItemInt(__pyx_v_query, __pyx_v_i, int, 1, __Pyx_PyInt_From_int, 0, 1, 1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 523, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_GetItemInt(__pyx_v_query, __pyx_v_i, int, 1, __Pyx_PyInt_From_int, 0, 1, 1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 528, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_3);
-      __pyx_t_8 = PyObject_RichCompare(__pyx_t_2, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_8); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 523, __pyx_L1_error)
+      __pyx_t_8 = PyObject_RichCompare(__pyx_t_2, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_8); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 528, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
       __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-      __pyx_t_7 = __Pyx_PyObject_IsTrue(__pyx_t_8); if (unlikely(__pyx_t_7 < 0)) __PYX_ERR(0, 523, __pyx_L1_error)
+      __pyx_t_7 = __Pyx_PyObject_IsTrue(__pyx_t_8); if (unlikely(__pyx_t_7 < 0)) __PYX_ERR(0, 528, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
       if (__pyx_t_7) {
 
@@ -5183,10 +4765,10 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   }
 
   /*else*/ {
-    __pyx_t_10 = __Pyx_PyObject_AsString(__pyx_v_ref_bytes); if (unlikely((!__pyx_t_10) && PyErr_Occurred())) __PYX_ERR(0, 526, __pyx_L1_error)
+    __pyx_t_10 = __Pyx_PyObject_AsString(__pyx_v_ref_bytes); if (unlikely((!__pyx_t_10) && PyErr_Occurred())) __PYX_ERR(0, 531, __pyx_L1_error)
     __pyx_v_r_ptr = __pyx_t_10;
 
-    __pyx_t_10 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_10) && PyErr_Occurred())) __PYX_ERR(0, 527, __pyx_L1_error)
+    __pyx_t_10 = __Pyx_PyObject_AsString(__pyx_v_query_bytes); if (unlikely((!__pyx_t_10) && PyErr_Occurred())) __PYX_ERR(0, 532, __pyx_L1_error)
     __pyx_v_q_ptr = __pyx_t_10;
 
     __pyx_t_6 = __pyx_v_length;
@@ -5204,15 +4786,15 @@ static PyObject *__pyx_pf_8cutadapt_6_align_6compare_prefixes(CYTHON_UNUSED PyOb
   __pyx_L5:;
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_length); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 533, __pyx_L1_error)
+  __pyx_t_8 = __Pyx_PyInt_From_int(__pyx_v_length); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 538, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_8);
-  __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_length); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 533, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_length); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 538, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
-  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_matches); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 533, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_matches); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 538, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_9 = __Pyx_PyInt_From_int((__pyx_v_length - __pyx_v_matches)); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 533, __pyx_L1_error)
+  __pyx_t_9 = __Pyx_PyInt_From_int((__pyx_v_length - __pyx_v_matches)); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 538, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_9);
-  __pyx_t_11 = PyTuple_New(6); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 533, __pyx_L1_error)
+  __pyx_t_11 = PyTuple_New(6); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 538, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_11);
   __Pyx_INCREF(__pyx_int_0);
   __Pyx_GIVEREF(__pyx_int_0);
@@ -5267,11 +4849,10 @@ static PyObject *__pyx_tp_new_8cutadapt_6_align_Aligner(PyTypeObject *t, PyObjec
   p->_dpmatrix = Py_None; Py_INCREF(Py_None);
   p->_reference = ((PyObject*)Py_None); Py_INCREF(Py_None);
   p->str_reference = ((PyObject*)Py_None); Py_INCREF(Py_None);
-  if (unlikely(__pyx_pw_8cutadapt_6_align_7Aligner_1__cinit__(o, a, k) < 0)) goto bad;
+  if (unlikely(__pyx_pw_8cutadapt_6_align_7Aligner_1__cinit__(o, a, k) < 0)) {
+    Py_DECREF(o); o = 0;
+  }
   return o;
-  bad:
-  Py_DECREF(o); o = 0;
-  return NULL;
 }
 
 static void __pyx_tp_dealloc_8cutadapt_6_align_Aligner(PyObject *o) {
@@ -5820,6 +5401,10 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_n_s_R, __pyx_k_R, sizeof(__pyx_k_R), 0, 0, 1, 1},
   {&__pyx_kp_s_Representation_of_the_dynamic_p, __pyx_k_Representation_of_the_dynamic_p, sizeof(__pyx_k_Representation_of_the_dynamic_p), 0, 0, 1, 0},
   {&__pyx_n_s_S, __pyx_k_S, sizeof(__pyx_k_S), 0, 0, 1, 1},
+  {&__pyx_n_s_START_WITHIN_QUERY, __pyx_k_START_WITHIN_QUERY, sizeof(__pyx_k_START_WITHIN_QUERY), 0, 0, 1, 1},
+  {&__pyx_n_s_START_WITHIN_REFERENCE, __pyx_k_START_WITHIN_REFERENCE, sizeof(__pyx_k_START_WITHIN_REFERENCE), 0, 0, 1, 1},
+  {&__pyx_n_s_STOP_WITHIN_QUERY, __pyx_k_STOP_WITHIN_QUERY, sizeof(__pyx_k_STOP_WITHIN_QUERY), 0, 0, 1, 1},
+  {&__pyx_n_s_STOP_WITHIN_REFERENCE, __pyx_k_STOP_WITHIN_REFERENCE, sizeof(__pyx_k_STOP_WITHIN_REFERENCE), 0, 0, 1, 1},
   {&__pyx_n_s_T, __pyx_k_T, sizeof(__pyx_k_T), 0, 0, 1, 1},
   {&__pyx_n_s_U, __pyx_k_U, sizeof(__pyx_k_U), 0, 0, 1, 1},
   {&__pyx_n_s_V, __pyx_k_V, sizeof(__pyx_k_V), 0, 0, 1, 1},
@@ -5899,8 +5484,8 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
 static int __Pyx_InitCachedBuiltins(void) {
   __pyx_builtin_range = __Pyx_GetBuiltinName(__pyx_n_s_range); if (!__pyx_builtin_range) __PYX_ERR(0, 98, __pyx_L1_error)
   __pyx_builtin_zip = __Pyx_GetBuiltinName(__pyx_n_s_zip); if (!__pyx_builtin_zip) __PYX_ERR(0, 113, __pyx_L1_error)
-  __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(0, 214, __pyx_L1_error)
-  __pyx_builtin_MemoryError = __Pyx_GetBuiltinName(__pyx_n_s_MemoryError); if (!__pyx_builtin_MemoryError) __PYX_ERR(0, 235, __pyx_L1_error)
+  __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(0, 219, __pyx_L1_error)
+  __pyx_builtin_MemoryError = __Pyx_GetBuiltinName(__pyx_n_s_MemoryError); if (!__pyx_builtin_MemoryError) __PYX_ERR(0, 240, __pyx_L1_error)
   return 0;
   __pyx_L1_error:;
   return -1;
@@ -5922,27 +5507,27 @@ static int __Pyx_InitCachedConstants(void) {
   __Pyx_GOTREF(__pyx_tuple__4);
   __Pyx_GIVEREF(__pyx_tuple__4);
 
-  __pyx_tuple__9 = PyTuple_Pack(1, __pyx_kp_s_Minimum_overlap_must_be_at_least); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(0, 214, __pyx_L1_error)
+  __pyx_tuple__9 = PyTuple_Pack(1, __pyx_kp_s_Minimum_overlap_must_be_at_least); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(0, 219, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__9);
   __Pyx_GIVEREF(__pyx_tuple__9);
 
-  __pyx_tuple__10 = PyTuple_Pack(1, __pyx_kp_s_Insertion_deletion_cost_must_be); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(0, 224, __pyx_L1_error)
+  __pyx_tuple__10 = PyTuple_Pack(1, __pyx_kp_s_Insertion_deletion_cost_must_be); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(0, 229, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__10);
   __Pyx_GIVEREF(__pyx_tuple__10);
 
-  __pyx_tuple__11 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 237, __pyx_L1_error)
+  __pyx_tuple__11 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 242, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__11);
   __Pyx_GIVEREF(__pyx_tuple__11);
 
-  __pyx_tuple__12 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__12)) __PYX_ERR(0, 275, __pyx_L1_error)
+  __pyx_tuple__12 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__12)) __PYX_ERR(0, 280, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__12);
   __Pyx_GIVEREF(__pyx_tuple__12);
 
-  __pyx_tuple__13 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__13)) __PYX_ERR(0, 502, __pyx_L1_error)
+  __pyx_tuple__13 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__13)) __PYX_ERR(0, 507, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__13);
   __Pyx_GIVEREF(__pyx_tuple__13);
 
-  __pyx_tuple__14 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__14)) __PYX_ERR(0, 503, __pyx_L1_error)
+  __pyx_tuple__14 = PyTuple_Pack(1, __pyx_n_s_ascii); if (unlikely(!__pyx_tuple__14)) __PYX_ERR(0, 508, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__14);
   __Pyx_GIVEREF(__pyx_tuple__14);
 
@@ -5971,15 +5556,15 @@ static int __Pyx_InitCachedConstants(void) {
   __Pyx_GIVEREF(__pyx_tuple__24);
   __pyx_codeobj__25 = (PyObject*)__Pyx_PyCode_New(1, 0, 8, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__24, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_str, 108, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__25)) __PYX_ERR(0, 108, __pyx_L1_error)
 
-  __pyx_tuple__26 = PyTuple_Pack(8, __pyx_n_s_reference, __pyx_n_s_query, __pyx_n_s_max_error_rate, __pyx_n_s_flags, __pyx_n_s_wildcard_ref, __pyx_n_s_wildcard_query, __pyx_n_s_min_overlap, __pyx_n_s_aligner); if (unlikely(!__pyx_tuple__26)) __PYX_ERR(0, 484, __pyx_L1_error)
+  __pyx_tuple__26 = PyTuple_Pack(8, __pyx_n_s_reference, __pyx_n_s_query, __pyx_n_s_max_error_rate, __pyx_n_s_flags, __pyx_n_s_wildcard_ref, __pyx_n_s_wildcard_query, __pyx_n_s_min_overlap, __pyx_n_s_aligner); if (unlikely(!__pyx_tuple__26)) __PYX_ERR(0, 489, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__26);
   __Pyx_GIVEREF(__pyx_tuple__26);
-  __pyx_codeobj__27 = (PyObject*)__Pyx_PyCode_New(7, 0, 8, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__26, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_locate, 484, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__27)) __PYX_ERR(0, 484, __pyx_L1_error)
+  __pyx_codeobj__27 = (PyObject*)__Pyx_PyCode_New(7, 0, 8, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__26, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_locate, 489, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__27)) __PYX_ERR(0, 489, __pyx_L1_error)
 
-  __pyx_tuple__28 = PyTuple_Pack(14, __pyx_n_s_ref, __pyx_n_s_query, __pyx_n_s_wildcard_ref, __pyx_n_s_wildcard_query, __pyx_n_s_m, __pyx_n_s_n, __pyx_n_s_query_bytes, __pyx_n_s_ref_bytes, __pyx_n_s_r_ptr, __pyx_n_s_q_ptr, __pyx_n_s_length, __pyx_n_s_i, __pyx_n_s_matches, __pyx_n_s_compare_ascii); if (unlikely(!__pyx_tuple__28)) __PYX_ERR(0, 490, __pyx_L1_error)
+  __pyx_tuple__28 = PyTuple_Pack(14, __pyx_n_s_ref, __pyx_n_s_query, __pyx_n_s_wildcard_ref, __pyx_n_s_wildcard_query, __pyx_n_s_m, __pyx_n_s_n, __pyx_n_s_query_bytes, __pyx_n_s_ref_bytes, __pyx_n_s_r_ptr, __pyx_n_s_q_ptr, __pyx_n_s_length, __pyx_n_s_i, __pyx_n_s_matches, __pyx_n_s_compare_ascii); if (unlikely(!__pyx_tuple__28)) __PYX_ERR(0, 495, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__28);
   __Pyx_GIVEREF(__pyx_tuple__28);
-  __pyx_codeobj__29 = (PyObject*)__Pyx_PyCode_New(4, 0, 14, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__28, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_compare_prefixes, 490, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__29)) __PYX_ERR(0, 490, __pyx_L1_error)
+  __pyx_codeobj__29 = (PyObject*)__Pyx_PyCode_New(4, 0, 14, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__28, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_compare_prefixes, 495, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__29)) __PYX_ERR(0, 495, __pyx_L1_error)
   __Pyx_RefNannyFinishContext();
   return 0;
   __pyx_L1_error:;
@@ -6123,7 +5708,7 @@ PyMODINIT_FUNC PyInit__align(void)
   __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_acgt_table); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 81, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __pyx_t_3 = NULL;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_2))) {
     __pyx_t_3 = PyMethod_GET_SELF(__pyx_t_2);
     if (likely(__pyx_t_3)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
@@ -6149,7 +5734,7 @@ PyMODINIT_FUNC PyInit__align(void)
   __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_iupac_table); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 82, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __pyx_t_3 = NULL;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_2))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_2))) {
     __pyx_t_3 = PyMethod_GET_SELF(__pyx_t_2);
     if (likely(__pyx_t_3)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
@@ -6196,14 +5781,26 @@ PyMODINIT_FUNC PyInit__align(void)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8cutadapt_6_align_5locate, NULL, __pyx_n_s_cutadapt__align); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 484, __pyx_L1_error)
+  if (PyDict_SetItem((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner->tp_dict, __pyx_n_s_START_WITHIN_REFERENCE, __pyx_int_1) < 0) __PYX_ERR(0, 195, __pyx_L1_error)
+  PyType_Modified(__pyx_ptype_8cutadapt_6_align_Aligner);
+
+  if (PyDict_SetItem((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner->tp_dict, __pyx_n_s_START_WITHIN_QUERY, __pyx_int_2) < 0) __PYX_ERR(0, 196, __pyx_L1_error)
+  PyType_Modified(__pyx_ptype_8cutadapt_6_align_Aligner);
+
+  if (PyDict_SetItem((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner->tp_dict, __pyx_n_s_STOP_WITHIN_REFERENCE, __pyx_int_4) < 0) __PYX_ERR(0, 197, __pyx_L1_error)
+  PyType_Modified(__pyx_ptype_8cutadapt_6_align_Aligner);
+
+  if (PyDict_SetItem((PyObject *)__pyx_ptype_8cutadapt_6_align_Aligner->tp_dict, __pyx_n_s_STOP_WITHIN_QUERY, __pyx_int_8) < 0) __PYX_ERR(0, 198, __pyx_L1_error)
+  PyType_Modified(__pyx_ptype_8cutadapt_6_align_Aligner);
+
+  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8cutadapt_6_align_5locate, NULL, __pyx_n_s_cutadapt__align); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 489, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_locate, __pyx_t_1) < 0) __PYX_ERR(0, 484, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_locate, __pyx_t_1) < 0) __PYX_ERR(0, 489, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8cutadapt_6_align_7compare_prefixes, NULL, __pyx_n_s_cutadapt__align); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 490, __pyx_L1_error)
+  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8cutadapt_6_align_7compare_prefixes, NULL, __pyx_n_s_cutadapt__align); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 495, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_compare_prefixes, __pyx_t_1) < 0) __PYX_ERR(0, 490, __pyx_L1_error)
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_compare_prefixes, __pyx_t_1) < 0) __PYX_ERR(0, 495, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
   __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
@@ -6301,7 +5898,7 @@ static int __Pyx_TryUnpackUnboundCMethod(__Pyx_CachedCFunction* target) {
     {
         PyMethodDescrObject *descr = (PyMethodDescrObject*) method;
         target->func = descr->d_method->ml_meth;
-        target->flag = descr->d_method->ml_flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
+        target->flag = descr->d_method->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_O | METH_NOARGS);
     }
 #endif
     return 0;
@@ -6311,7 +5908,7 @@ static int __Pyx_TryUnpackUnboundCMethod(__Pyx_CachedCFunction* target) {
 static PyObject* __Pyx__CallUnboundCMethod0(__Pyx_CachedCFunction* cfunc, PyObject* self) {
     PyObject *args, *result = NULL;
     if (unlikely(!cfunc->method) && unlikely(__Pyx_TryUnpackUnboundCMethod(cfunc) < 0)) return NULL;
-#if CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
     args = PyTuple_New(1);
     if (unlikely(!args)) goto bad;
     Py_INCREF(self);
@@ -6349,7 +5946,7 @@ static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
 
 /* IterFinish */
 static CYTHON_INLINE int __Pyx_IterFinish(void) {
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
     PyThreadState *tstate = PyThreadState_GET();
     PyObject* exc_type = tstate->curexc_type;
     if (unlikely(exc_type)) {
@@ -6474,144 +6071,6 @@ static CYTHON_INLINE int __Pyx_SetItemInt_ByteArray_Fast(PyObject* string, Py_ss
     }
 }
 
-/* PyCFunctionFastCall */
-#if CYTHON_FAST_PYCCALL
-static CYTHON_INLINE PyObject * __Pyx_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, Py_ssize_t nargs) {
-    PyCFunctionObject *func = (PyCFunctionObject*)func_obj;
-    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
-    PyObject *self = PyCFunction_GET_SELF(func);
-    assert(PyCFunction_Check(func));
-    assert(METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)));
-    assert(nargs >= 0);
-    assert(nargs == 0 || args != NULL);
-    /* _PyCFunction_FastCallDict() must not be called with an exception set,
-       because it may clear it (directly or indirectly) and so the
-       caller loses its exception */
-    assert(!PyErr_Occurred());
-    return (*((__Pyx_PyCFunctionFast)meth)) (self, args, nargs, NULL);
-}
-#endif  // CYTHON_FAST_PYCCALL
-
-/* PyFunctionFastCall */
-#if CYTHON_FAST_PYCALL
-#include "frameobject.h"
-static PyObject* __Pyx_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t na,
-                                               PyObject *globals) {
-    PyFrameObject *f;
-    PyThreadState *tstate = PyThreadState_GET();
-    PyObject **fastlocals;
-    Py_ssize_t i;
-    PyObject *result;
-    assert(globals != NULL);
-    /* XXX Perhaps we should create a specialized
-       PyFrame_New() that doesn't take locals, but does
-       take builtins without sanity checking them.
-       */
-    assert(tstate != NULL);
-    f = PyFrame_New(tstate, co, globals, NULL);
-    if (f == NULL) {
-        return NULL;
-    }
-    fastlocals = f->f_localsplus;
-    for (i = 0; i < na; i++) {
-        Py_INCREF(*args);
-        fastlocals[i] = *args++;
-    }
-    result = PyEval_EvalFrameEx(f,0);
-    ++tstate->recursion_depth;
-    Py_DECREF(f);
-    --tstate->recursion_depth;
-    return result;
-}
-#if 1 || PY_VERSION_HEX < 0x030600B1
-static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs) {
-    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
-    PyObject *globals = PyFunction_GET_GLOBALS(func);
-    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
-    PyObject *closure;
-#if PY_MAJOR_VERSION >= 3
-    PyObject *kwdefs;
-#endif
-    PyObject *kwtuple, **k;
-    PyObject **d;
-    Py_ssize_t nd;
-    Py_ssize_t nk;
-    PyObject *result;
-    assert(kwargs == NULL || PyDict_Check(kwargs));
-    nk = kwargs ? PyDict_Size(kwargs) : 0;
-    if (Py_EnterRecursiveCall((char*)" while calling a Python object")) {
-        return NULL;
-    }
-    if (
-#if PY_MAJOR_VERSION >= 3
-            co->co_kwonlyargcount == 0 &&
-#endif
-            likely(kwargs == NULL || nk == 0) &&
-            co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
-        if (argdefs == NULL && co->co_argcount == nargs) {
-            result = __Pyx_PyFunction_FastCallNoKw(co, args, nargs, globals);
-            goto done;
-        }
-        else if (nargs == 0 && argdefs != NULL
-                 && co->co_argcount == Py_SIZE(argdefs)) {
-            /* function called with no arguments, but all parameters have
-               a default value: use default values as arguments .*/
-            args = &PyTuple_GET_ITEM(argdefs, 0);
-            result =__Pyx_PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs), globals);
-            goto done;
-        }
-    }
-    if (kwargs != NULL) {
-        Py_ssize_t pos, i;
-        kwtuple = PyTuple_New(2 * nk);
-        if (kwtuple == NULL) {
-            result = NULL;
-            goto done;
-        }
-        k = &PyTuple_GET_ITEM(kwtuple, 0);
-        pos = i = 0;
-        while (PyDict_Next(kwargs, &pos, &k[i], &k[i+1])) {
-            Py_INCREF(k[i]);
-            Py_INCREF(k[i+1]);
-            i += 2;
-        }
-        nk = i / 2;
-    }
-    else {
-        kwtuple = NULL;
-        k = NULL;
-    }
-    closure = PyFunction_GET_CLOSURE(func);
-#if PY_MAJOR_VERSION >= 3
-    kwdefs = PyFunction_GET_KW_DEFAULTS(func);
-#endif
-    if (argdefs != NULL) {
-        d = &PyTuple_GET_ITEM(argdefs, 0);
-        nd = Py_SIZE(argdefs);
-    }
-    else {
-        d = NULL;
-        nd = 0;
-    }
-#if PY_MAJOR_VERSION >= 3
-    result = PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL,
-                               args, nargs,
-                               k, (int)nk,
-                               d, (int)nd, kwdefs, closure);
-#else
-    result = PyEval_EvalCodeEx(co, globals, (PyObject *)NULL,
-                               args, nargs,
-                               k, (int)nk,
-                               d, (int)nd, closure);
-#endif
-    Py_XDECREF(kwtuple);
-done:
-    Py_LeaveRecursiveCall();
-    return result;
-}
-#endif  // CPython < 3.6
-#endif  // CYTHON_FAST_PYCALL
-
 /* PyObjectCallMethO */
 #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) {
@@ -6645,11 +6104,6 @@ static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) {
     return result;
 }
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
-#if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(func)) {
-        return __Pyx_PyFunction_FastCall(func, &arg, 1);
-    }
-#endif
 #ifdef __Pyx_CyFunction_USED
     if (likely(PyCFunction_Check(func) || PyObject_TypeCheck(func, __pyx_CyFunctionType))) {
 #else
@@ -6657,10 +6111,6 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObjec
 #endif
         if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) {
             return __Pyx_PyObject_CallMethO(func, arg);
-#if CYTHON_FAST_PYCCALL
-        } else if (PyCFunction_GET_FLAGS(func) & METH_FASTCALL) {
-            return __Pyx_PyCFunction_FastCall(func, &arg, 1);
-#endif
         }
     }
     return __Pyx__PyObject_CallOneArg(func, arg);
@@ -6679,11 +6129,6 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObjec
 /* PyObjectCallNoArg */
   #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func) {
-#if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(func)) {
-        return __Pyx_PyFunction_FastCall(func, NULL, 0);
-    }
-#endif
 #ifdef __Pyx_CyFunction_USED
     if (likely(PyCFunction_Check(func) || PyObject_TypeCheck(func, __pyx_CyFunctionType))) {
 #else
@@ -6840,7 +6285,7 @@ bad:
 }
 
 /* PyIntBinop */
-    #if !CYTHON_COMPILING_IN_PYPY
+    #if CYTHON_COMPILING_IN_CPYTHON
 static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, CYTHON_UNUSED int inplace) {
     #if PY_MAJOR_VERSION < 3
     if (likely(PyInt_CheckExact(op1))) {
@@ -6853,14 +6298,12 @@ static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED
             return PyLong_Type.tp_as_number->nb_add(op1, op2);
     }
     #endif
-    #if CYTHON_USE_PYLONG_INTERNALS
+    #if CYTHON_USE_PYLONG_INTERNALS && PY_MAJOR_VERSION >= 3
     if (likely(PyLong_CheckExact(op1))) {
         const long b = intval;
         long a, x;
-#ifdef HAVE_LONG_LONG
         const PY_LONG_LONG llb = intval;
         PY_LONG_LONG lla, llx;
-#endif
         const digit* digits = ((PyLongObject*)op1)->ob_digit;
         const Py_ssize_t size = Py_SIZE(op1);
         if (likely(__Pyx_sst_abs(size) <= 1)) {
@@ -6872,74 +6315,58 @@ static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED
                     if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) {
                         a = -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 2 * PyLong_SHIFT) {
                         lla = -(PY_LONG_LONG) (((((unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 case 2:
                     if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) {
                         a = (long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 2 * PyLong_SHIFT) {
                         lla = (PY_LONG_LONG) (((((unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 case -3:
                     if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) {
                         a = -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 3 * PyLong_SHIFT) {
                         lla = -(PY_LONG_LONG) (((((((unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 case 3:
                     if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) {
                         a = (long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 3 * PyLong_SHIFT) {
                         lla = (PY_LONG_LONG) (((((((unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 case -4:
                     if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) {
                         a = -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 4 * PyLong_SHIFT) {
                         lla = -(PY_LONG_LONG) (((((((((unsigned PY_LONG_LONG)digits[3]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 case 4:
                     if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) {
                         a = (long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]));
                         break;
-#ifdef HAVE_LONG_LONG
                     } else if (8 * sizeof(PY_LONG_LONG) - 1 > 4 * PyLong_SHIFT) {
                         lla = (PY_LONG_LONG) (((((((((unsigned PY_LONG_LONG)digits[3]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[2]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[1]) << PyLong_SHIFT) | (unsigned PY_LONG_LONG)digits[0]));
                         goto long_long;
-#endif
                     }
                 default: return PyLong_Type.tp_as_number->nb_add(op1, op2);
             }
         }
                 x = a + b;
             return PyLong_FromLong(x);
-#ifdef HAVE_LONG_LONG
         long_long:
                 llx = lla + llb;
             return PyLong_FromLongLong(llx);
-#endif
-        
-        
     }
     #endif
     if (PyFloat_CheckExact(op1)) {
@@ -6966,7 +6393,7 @@ static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, CYTHON_UNUSED
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
                                                               CYTHON_NCP_UNUSED int wraparound,
                                                               CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
     if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
         PyObject *r = PyList_GET_ITEM(o, i);
@@ -6981,7 +6408,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
                                                               CYTHON_NCP_UNUSED int wraparound,
                                                               CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
     if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
         PyObject *r = PyTuple_GET_ITEM(o, i);
@@ -6996,7 +6423,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list,
                                                      CYTHON_NCP_UNUSED int wraparound,
                                                      CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (is_list || PyList_CheckExact(o)) {
         Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
         if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
@@ -7046,7 +6473,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
 }
 static CYTHON_INLINE int __Pyx_SetItemInt_Fast(PyObject *o, Py_ssize_t i, PyObject *v, int is_list,
                                                CYTHON_NCP_UNUSED int wraparound, CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (is_list || PyList_CheckExact(o)) {
         Py_ssize_t n = (!wraparound) ? i : ((likely(i >= 0)) ? i : i + PyList_GET_SIZE(o));
         if ((!boundscheck) || likely((n >= 0) & (n < PyList_GET_SIZE(o)))) {
@@ -7124,7 +6551,7 @@ static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, in
 }
 
 /* PyErrFetchRestore */
-      #if CYTHON_FAST_THREAD_STATE
+      #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) {
     PyObject *tmp_type, *tmp_value, *tmp_tb;
     tmp_type = tstate->curexc_type;
@@ -7313,7 +6740,7 @@ bad:
 /* GetModuleGlobalName */
         static CYTHON_INLINE PyObject *__Pyx_GetModuleGlobalName(PyObject *name) {
     PyObject *result;
-#if !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
     result = PyDict_GetItem(__pyx_d, name);
     if (likely(result)) {
         Py_INCREF(result);
@@ -7525,7 +6952,7 @@ __Pyx_CyFunction_init_defaults(__pyx_CyFunctionObject *op) {
     PyObject *res = op->defaults_getter((PyObject *) op);
     if (unlikely(!res))
         return -1;
-    #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+    #if CYTHON_COMPILING_IN_CPYTHON
     op->defaults_tuple = PyTuple_GET_ITEM(res, 0);
     Py_INCREF(op->defaults_tuple);
     op->defaults_kwdict = PyTuple_GET_ITEM(res, 1);
@@ -7784,9 +7211,11 @@ __Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
                                PyString_AsString(op->func_qualname), (void *)op);
 #endif
 }
-static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, PyObject *arg, PyObject *kw) {
+#if CYTHON_COMPILING_IN_PYPY
+static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
     PyCFunctionObject* f = (PyCFunctionObject*)func;
     PyCFunction meth = f->m_ml->ml_meth;
+    PyObject *self = f->m_self;
     Py_ssize_t size;
     switch (f->m_ml->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
     case METH_VARARGS:
@@ -7832,32 +7261,11 @@ static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, Py
                  f->m_ml->ml_name);
     return NULL;
 }
-static CYTHON_INLINE PyObject *__Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
-    return __Pyx_CyFunction_CallMethod(func, ((PyCFunctionObject*)func)->m_self, arg, kw);
-}
-static PyObject *__Pyx_CyFunction_CallAsMethod(PyObject *func, PyObject *args, PyObject *kw) {
-    PyObject *result;
-    __pyx_CyFunctionObject *cyfunc = (__pyx_CyFunctionObject *) func;
-    if ((cyfunc->flags & __Pyx_CYFUNCTION_CCLASS) && !(cyfunc->flags & __Pyx_CYFUNCTION_STATICMETHOD)) {
-        Py_ssize_t argc;
-        PyObject *new_args;
-        PyObject *self;
-        argc = PyTuple_GET_SIZE(args);
-        new_args = PyTuple_GetSlice(args, 1, argc);
-        if (unlikely(!new_args))
-            return NULL;
-        self = PyTuple_GetItem(args, 0);
-        if (unlikely(!self)) {
-            Py_DECREF(new_args);
-            return NULL;
-        }
-        result = __Pyx_CyFunction_CallMethod(func, self, new_args, kw);
-        Py_DECREF(new_args);
-    } else {
-        result = __Pyx_CyFunction_Call(func, args, kw);
-    }
-    return result;
+#else
+static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
+	return PyCFunction_Call(func, arg, kw);
 }
+#endif
 static PyTypeObject __pyx_CyFunctionType_type = {
     PyVarObject_HEAD_INIT(0, 0)
     "cython_function_or_method",
@@ -7877,7 +7285,7 @@ static PyTypeObject __pyx_CyFunctionType_type = {
     0,
     0,
     0,
-    __Pyx_CyFunction_CallAsMethod,
+    __Pyx_CyFunction_Call,
     0,
     0,
     0,
@@ -7919,6 +7327,9 @@ static PyTypeObject __pyx_CyFunctionType_type = {
 #endif
 };
 static int __pyx_CyFunction_init(void) {
+#if !CYTHON_COMPILING_IN_PYPY
+    __pyx_CyFunctionType_type.tp_call = PyCFunction_Call;
+#endif
     __pyx_CyFunctionType = __Pyx_FetchCommonType(&__pyx_CyFunctionType_type);
     if (__pyx_CyFunctionType == NULL) {
         return -1;
@@ -8210,7 +7621,7 @@ static void __Pyx_AddTraceback(const char *funcname, int c_line,
         0                    /*PyObject *locals*/
     );
     if (!py_frame) goto bad;
-    __Pyx_PyFrame_SetLineNumber(py_frame, py_line);
+    py_frame->f_lineno = py_line;
     PyTraceBack_Here(py_frame);
 bad:
     Py_XDECREF(py_code);
@@ -8248,18 +7659,14 @@ bad:
             return PyInt_FromLong((long) value);
         } else if (sizeof(long) <= sizeof(unsigned long)) {
             return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
             return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
         }
     } else {
         if (sizeof(long) <= sizeof(long)) {
             return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
             return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
         }
     }
     {
@@ -8279,18 +7686,14 @@ bad:
             return PyInt_FromLong((long) value);
         } else if (sizeof(int) <= sizeof(unsigned long)) {
             return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
             return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
         }
     } else {
         if (sizeof(int) <= sizeof(long)) {
             return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
             return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
         }
     }
     {
@@ -8369,10 +7772,8 @@ bad:
 #endif
             if (sizeof(int) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -8439,10 +7840,8 @@ bad:
 #endif
             if (sizeof(int) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -8558,10 +7957,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(unsigned char) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(unsigned char, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(unsigned char) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(unsigned char, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -8628,10 +8025,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(unsigned char) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(unsigned char, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(unsigned char) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(unsigned char, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -8747,10 +8142,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(long) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -8817,10 +8210,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(long) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -8869,7 +8260,7 @@ raise_neg_overflow:
 }
 
 /* SwapException */
-              #if CYTHON_FAST_THREAD_STATE
+              #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE void __Pyx__ExceptionSwap(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) {
     PyObject *tmp_type, *tmp_value, *tmp_tb;
     tmp_type = tstate->exc_type;
@@ -8897,29 +8288,15 @@ static CYTHON_INLINE void __Pyx_ExceptionSwap(PyObject **type, PyObject **value,
               static PyObject* __Pyx_PyObject_CallMethod1(PyObject* obj, PyObject* method_name, PyObject* arg) {
     PyObject *method, *result = NULL;
     method = __Pyx_PyObject_GetAttrStr(obj, method_name);
-    if (unlikely(!method)) goto done;
-#if CYTHON_UNPACK_METHODS
+    if (unlikely(!method)) goto bad;
+#if CYTHON_COMPILING_IN_CPYTHON
     if (likely(PyMethod_Check(method))) {
         PyObject *self = PyMethod_GET_SELF(method);
         if (likely(self)) {
             PyObject *args;
             PyObject *function = PyMethod_GET_FUNCTION(method);
-            #if CYTHON_FAST_PYCALL
-            if (PyFunction_Check(function)) {
-                PyObject *args[2] = {self, arg};
-                result = __Pyx_PyFunction_FastCall(function, args, 2);
-                goto done;
-            }
-            #endif
-            #if CYTHON_FAST_PYCCALL
-            if (__Pyx_PyFastCFunction_Check(function)) {
-                PyObject *args[2] = {self, arg};
-                result = __Pyx_PyCFunction_FastCall(function, args, 2);
-                goto done;
-            }
-            #endif
             args = PyTuple_New(2);
-            if (unlikely(!args)) goto done;
+            if (unlikely(!args)) goto bad;
             Py_INCREF(self);
             PyTuple_SET_ITEM(args, 0, self);
             Py_INCREF(arg);
@@ -8934,7 +8311,7 @@ static CYTHON_INLINE void __Pyx_ExceptionSwap(PyObject **type, PyObject **value,
     }
 #endif
     result = __Pyx_PyObject_CallOneArg(method, arg);
-done:
+bad:
     Py_XDECREF(method);
     return result;
 }
@@ -8961,38 +8338,41 @@ static int __Pyx_PyGen_FetchStopIterationValue(PyObject **pvalue) {
         return 0;
     }
     if (likely(et == PyExc_StopIteration)) {
-        if (!ev) {
-            Py_INCREF(Py_None);
-            value = Py_None;
-        }
 #if PY_VERSION_HEX >= 0x030300A0
-        else if (Py_TYPE(ev) == (PyTypeObject*)PyExc_StopIteration) {
+        if (ev && Py_TYPE(ev) == (PyTypeObject*)PyExc_StopIteration) {
             value = ((PyStopIterationObject *)ev)->value;
             Py_INCREF(value);
             Py_DECREF(ev);
+            Py_XDECREF(tb);
+            Py_DECREF(et);
+            *pvalue = value;
+            return 0;
         }
 #endif
-        else if (unlikely(PyTuple_Check(ev))) {
-            if (PyTuple_GET_SIZE(ev) >= 1) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-                value = PyTuple_GET_ITEM(ev, 0);
-                Py_INCREF(value);
+        if (!ev || !PyObject_TypeCheck(ev, (PyTypeObject*)PyExc_StopIteration)) {
+            if (!ev) {
+                Py_INCREF(Py_None);
+                ev = Py_None;
+            } else if (PyTuple_Check(ev)) {
+                if (PyTuple_GET_SIZE(ev) >= 1) {
+                    PyObject *value;
+#if CYTHON_COMPILING_IN_CPYTHON
+                    value = PySequence_ITEM(ev, 0);
 #else
-                value = PySequence_ITEM(ev, 0);
+                    value = PyTuple_GET_ITEM(ev, 0);
+                    Py_INCREF(value);
 #endif
-            } else {
-                Py_INCREF(Py_None);
-                value = Py_None;
+                    Py_DECREF(ev);
+                    ev = value;
+                } else {
+                    Py_INCREF(Py_None);
+                    Py_DECREF(ev);
+                    ev = Py_None;
+                }
             }
-            Py_DECREF(ev);
-        }
-        else if (!PyObject_TypeCheck(ev, (PyTypeObject*)PyExc_StopIteration)) {
-            value = ev;
-        }
-        if (likely(value)) {
             Py_XDECREF(tb);
             Py_DECREF(et);
-            *pvalue = value;
+            *pvalue = ev;
             return 0;
         }
     } else if (!PyErr_GivenExceptionMatches(et, PyExc_StopIteration)) {
@@ -9069,7 +8449,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value) {
     }
     __Pyx_PyThreadState_assign
     if (value) {
-#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
+#if CYTHON_COMPILING_IN_PYPY
 #else
         if (self->exc_traceback) {
             PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback;
@@ -9090,7 +8470,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value) {
     if (retval) {
         __Pyx_ExceptionSwap(&self->exc_type, &self->exc_value,
                             &self->exc_traceback);
-#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
+#if CYTHON_COMPILING_IN_PYPY
 #else
         if (self->exc_traceback) {
             PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback;
@@ -9200,12 +8580,7 @@ static PyObject *__Pyx_Generator_Next(PyObject *self) {
     if (yf) {
         PyObject *ret;
         gen->is_running = 1;
-        #ifdef __Pyx_Generator_USED
-        if (__Pyx_Generator_CheckExact(yf)) {
-            ret = __Pyx_Generator_Next(yf);
-        } else
-        #endif
-            ret = Py_TYPE(yf)->tp_iternext(yf);
+        ret = Py_TYPE(yf)->tp_iternext(yf);
         gen->is_running = 0;
         if (likely(ret)) {
             return ret;
@@ -9394,10 +8769,8 @@ static void __Pyx_Coroutine_del(PyObject *self) {
 static PyObject *
 __Pyx_Coroutine_get_name(__pyx_CoroutineObject *self)
 {
-    PyObject *name = self->gi_name;
-    if (unlikely(!name)) name = Py_None;
-    Py_INCREF(name);
-    return name;
+    Py_INCREF(self->gi_name);
+    return self->gi_name;
 }
 static int
 __Pyx_Coroutine_set_name(__pyx_CoroutineObject *self, PyObject *value)
@@ -9421,10 +8794,8 @@ __Pyx_Coroutine_set_name(__pyx_CoroutineObject *self, PyObject *value)
 static PyObject *
 __Pyx_Coroutine_get_qualname(__pyx_CoroutineObject *self)
 {
-    PyObject *name = self->gi_qualname;
-    if (unlikely(!name)) name = Py_None;
-    Py_INCREF(name);
-    return name;
+    Py_INCREF(self->gi_qualname);
+    return self->gi_qualname;
 }
 static int
 __Pyx_Coroutine_set_qualname(__pyx_CoroutineObject *self, PyObject *value)
@@ -9445,9 +8816,8 @@ __Pyx_Coroutine_set_qualname(__pyx_CoroutineObject *self, PyObject *value)
     Py_XDECREF(tmp);
     return 0;
 }
-static __pyx_CoroutineObject *__Pyx__Coroutine_New(
-            PyTypeObject* type, __pyx_coroutine_body_t body, PyObject *closure,
-            PyObject *name, PyObject *qualname, PyObject *module_name) {
+static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject* type, __pyx_coroutine_body_t body,
+                                                   PyObject *closure, PyObject *name, PyObject *qualname) {
     __pyx_CoroutineObject *gen = PyObject_GC_New(__pyx_CoroutineObject, type);
     if (gen == NULL)
         return NULL;
@@ -9466,8 +8836,6 @@ static __pyx_CoroutineObject *__Pyx__Coroutine_New(
     gen->gi_qualname = qualname;
     Py_XINCREF(name);
     gen->gi_name = name;
-    Py_XINCREF(module_name);
-    gen->gi_modulename = module_name;
     PyObject_GC_Track(gen);
     return gen;
 }
@@ -9773,9 +9141,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject* x) {
    else return PyObject_IsTrue(x);
 }
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
-#if CYTHON_USE_TYPE_SLOTS
   PyNumberMethods *m;
-#endif
   const char *name = NULL;
   PyObject *res = NULL;
 #if PY_MAJOR_VERSION < 3
@@ -9784,9 +9150,8 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
   if (PyLong_Check(x))
 #endif
     return __Pyx_NewRef(x);
-#if CYTHON_USE_TYPE_SLOTS
   m = Py_TYPE(x)->tp_as_number;
-  #if PY_MAJOR_VERSION < 3
+#if PY_MAJOR_VERSION < 3
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Int(x);
@@ -9795,14 +9160,11 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
     name = "long";
     res = PyNumber_Long(x);
   }
-  #else
+#else
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Long(x);
   }
-  #endif
-#else
-  res = PyNumber_Int(x);
 #endif
   if (res) {
 #if PY_MAJOR_VERSION < 3
diff --git a/cutadapt/_align.pyx b/src/cutadapt/_align.pyx
similarity index 99%
rename from cutadapt/_align.pyx
rename to src/cutadapt/_align.pyx
index 477a676..733ebc7 100644
--- a/cutadapt/_align.pyx
+++ b/src/cutadapt/_align.pyx
@@ -192,6 +192,11 @@ cdef class Aligner:
 	cdef bytes _reference  # TODO rename to translated_reference or so
 	cdef str str_reference
 
+	START_WITHIN_REFERENCE = 1
+	START_WITHIN_QUERY = 2
+	STOP_WITHIN_REFERENCE = 4
+	STOP_WITHIN_QUERY = 8
+
 	def __cinit__(self, str reference, double max_error_rate, int flags=SEMIGLOBAL, bint wildcard_ref=False, bint wildcard_query=False):
 		self.max_error_rate = max_error_rate
 		self.flags = flags
diff --git a/cutadapt/_qualtrim.c b/src/cutadapt/_qualtrim.c
similarity index 94%
rename from cutadapt/_qualtrim.c
rename to src/cutadapt/_qualtrim.c
index a0a36f9..ad342d7 100644
--- a/cutadapt/_qualtrim.c
+++ b/src/cutadapt/_qualtrim.c
@@ -1,9 +1,8 @@
-/* Generated by Cython 0.25.2 */
+/* Generated by Cython 0.24 */
 
 /* BEGIN: Cython Metadata
 {
-    "distutils": {},
-    "module_name": "cutadapt._qualtrim"
+    "distutils": {}
 }
 END: Cython Metadata */
 
@@ -14,7 +13,7 @@ END: Cython Metadata */
 #elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03020000)
     #error Cython requires Python 2.6+ or Python 3.2+.
 #else
-#define CYTHON_ABI "0_25_2"
+#define CYTHON_ABI "0_24"
 #include <stddef.h>
 #ifndef offsetof
   #define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
@@ -36,11 +35,6 @@ END: Cython Metadata */
 #ifndef DL_EXPORT
   #define DL_EXPORT(t) t
 #endif
-#ifndef HAVE_LONG_LONG
-  #if PY_VERSION_HEX >= 0x03030000 || (PY_MAJOR_VERSION == 2 && PY_VERSION_HEX >= 0x02070000)
-    #define HAVE_LONG_LONG
-  #endif
-#endif
 #ifndef PY_LONG_LONG
   #define PY_LONG_LONG LONG_LONG
 #endif
@@ -49,110 +43,13 @@ END: Cython Metadata */
 #endif
 #ifdef PYPY_VERSION
   #define CYTHON_COMPILING_IN_PYPY 1
-  #define CYTHON_COMPILING_IN_PYSTON 0
-  #define CYTHON_COMPILING_IN_CPYTHON 0
-  #undef CYTHON_USE_TYPE_SLOTS
-  #define CYTHON_USE_TYPE_SLOTS 0
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_INTERNALS
-  #define CYTHON_USE_UNICODE_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #undef CYTHON_AVOID_BORROWED_REFS
-  #define CYTHON_AVOID_BORROWED_REFS 1
-  #undef CYTHON_ASSUME_SAFE_MACROS
-  #define CYTHON_ASSUME_SAFE_MACROS 0
-  #undef CYTHON_UNPACK_METHODS
-  #define CYTHON_UNPACK_METHODS 0
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
-#elif defined(PYSTON_VERSION)
-  #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 1
   #define CYTHON_COMPILING_IN_CPYTHON 0
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
 #else
   #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 0
   #define CYTHON_COMPILING_IN_CPYTHON 1
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #if PY_MAJOR_VERSION < 3
-    #undef CYTHON_USE_ASYNC_SLOTS
-    #define CYTHON_USE_ASYNC_SLOTS 0
-  #elif !defined(CYTHON_USE_ASYNC_SLOTS)
-    #define CYTHON_USE_ASYNC_SLOTS 1
-  #endif
-  #if PY_VERSION_HEX < 0x02070000
-    #undef CYTHON_USE_PYLONG_INTERNALS
-    #define CYTHON_USE_PYLONG_INTERNALS 0
-  #elif !defined(CYTHON_USE_PYLONG_INTERNALS)
-    #define CYTHON_USE_PYLONG_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_PYLIST_INTERNALS
-    #define CYTHON_USE_PYLIST_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #if PY_VERSION_HEX < 0x030300F0
-    #undef CYTHON_USE_UNICODE_WRITER
-    #define CYTHON_USE_UNICODE_WRITER 0
-  #elif !defined(CYTHON_USE_UNICODE_WRITER)
-    #define CYTHON_USE_UNICODE_WRITER 1
-  #endif
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #ifndef CYTHON_FAST_THREAD_STATE
-    #define CYTHON_FAST_THREAD_STATE 1
-  #endif
-  #ifndef CYTHON_FAST_PYCALL
-    #define CYTHON_FAST_PYCALL 1
-  #endif
 #endif
-#if !defined(CYTHON_FAST_PYCCALL)
-#define CYTHON_FAST_PYCCALL  (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1)
+#if !defined(CYTHON_USE_PYLONG_INTERNALS) && CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x02070000
+  #define CYTHON_USE_PYLONG_INTERNALS 1
 #endif
 #if CYTHON_USE_PYLONG_INTERNALS
   #include "longintrepr.h"
@@ -188,44 +85,24 @@ END: Cython Metadata */
 #ifndef Py_TPFLAGS_HAVE_FINALIZE
   #define Py_TPFLAGS_HAVE_FINALIZE 0
 #endif
-#ifndef METH_FASTCALL
-  #define METH_FASTCALL 0x80
-  typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject **args,
-                                              Py_ssize_t nargs, PyObject *kwnames);
-#else
-  #define __Pyx_PyCFunctionFast _PyCFunctionFast
-#endif
-#if CYTHON_FAST_PYCCALL
-#define __Pyx_PyFastCFunction_Check(func)\
-    ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)))))
-#else
-#define __Pyx_PyFastCFunction_Check(func) 0
-#endif
 #if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
   #define CYTHON_PEP393_ENABLED 1
   #define __Pyx_PyUnicode_READY(op)       (likely(PyUnicode_IS_READY(op)) ?\
                                               0 : _PyUnicode_Ready((PyObject *)(op)))
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_LENGTH(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i)
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   PyUnicode_MAX_CHAR_VALUE(u)
   #define __Pyx_PyUnicode_KIND(u)         PyUnicode_KIND(u)
   #define __Pyx_PyUnicode_DATA(u)         PyUnicode_DATA(u)
   #define __Pyx_PyUnicode_READ(k, d, i)   PyUnicode_READ(k, d, i)
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  PyUnicode_WRITE(k, d, i, ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u)))
 #else
   #define CYTHON_PEP393_ENABLED 0
-  #define PyUnicode_1BYTE_KIND  1
-  #define PyUnicode_2BYTE_KIND  2
-  #define PyUnicode_4BYTE_KIND  4
   #define __Pyx_PyUnicode_READY(op)       (0)
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_SIZE(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i]))
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111)
   #define __Pyx_PyUnicode_KIND(u)         (sizeof(Py_UNICODE))
   #define __Pyx_PyUnicode_DATA(u)         ((void*)PyUnicode_AS_UNICODE(u))
   #define __Pyx_PyUnicode_READ(k, d, i)   ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i]))
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  (((void)(k)), ((Py_UNICODE*)d)[i] = ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != PyUnicode_GET_SIZE(u))
 #endif
 #if CYTHON_COMPILING_IN_PYPY
@@ -239,9 +116,6 @@ END: Cython Metadata */
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains)
   #define PyUnicode_Contains(u, s)  PySequence_Contains(u, s)
 #endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check)
-  #define PyByteArray_Check(obj)  PyObject_TypeCheck(obj, &PyByteArray_Type)
-#endif
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format)
   #define PyObject_Format(obj, fmt)  PyObject_CallMethod(obj, "__format__", "O", fmt)
 #endif
@@ -250,13 +124,6 @@ END: Cython Metadata */
   #define PyObject_Free(p)     PyMem_Free(p)
   #define PyObject_Realloc(p)  PyMem_Realloc(p)
 #endif
-#if CYTHON_COMPILING_IN_PYSTON
-  #define __Pyx_PyCode_HasFreeVars(co)  PyCode_HasFreeVars(co)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno)
-#else
-  #define __Pyx_PyCode_HasFreeVars(co)  (PyCode_GetNumFree(co) > 0)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno)  (frame)->f_lineno = (lineno)
-#endif
 #define __Pyx_PyString_FormatSafe(a, b)   ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b))
 #define __Pyx_PyUnicode_FormatSafe(a, b)  ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b))
 #if PY_MAJOR_VERSION >= 3
@@ -285,7 +152,6 @@ END: Cython Metadata */
   #define PySet_CheckExact(obj)        (Py_TYPE(obj) == &PySet_Type)
 #endif
 #define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type)
-#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception)
 #if PY_MAJOR_VERSION >= 3
   #define PyIntObject                  PyLongObject
   #define PyInt_Type                   PyLong_Type
@@ -324,20 +190,18 @@ END: Cython Metadata */
 #else
   #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
 #endif
-#if CYTHON_USE_ASYNC_SLOTS
-  #if PY_VERSION_HEX >= 0x030500B1
-    #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
-    #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
-  #else
-    typedef struct {
-        unaryfunc am_await;
-        unaryfunc am_aiter;
-        unaryfunc am_anext;
-    } __Pyx_PyAsyncMethodsStruct;
-    #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
-  #endif
+#if PY_VERSION_HEX >= 0x030500B1
+#define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
+#define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
+#elif CYTHON_COMPILING_IN_CPYTHON && PY_MAJOR_VERSION >= 3
+typedef struct {
+    unaryfunc am_await;
+    unaryfunc am_aiter;
+    unaryfunc am_anext;
+} __Pyx_PyAsyncMethodsStruct;
+#define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
 #else
-  #define __Pyx_PyType_AsAsync(obj) NULL
+#define __Pyx_PyType_AsAsync(obj) NULL
 #endif
 #ifndef CYTHON_RESTRICT
   #if defined(__GNUC__)
@@ -350,39 +214,10 @@ END: Cython Metadata */
     #define CYTHON_RESTRICT
   #endif
 #endif
-#ifndef CYTHON_UNUSED
-# if defined(__GNUC__)
-#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
-#     define CYTHON_UNUSED __attribute__ ((__unused__))
-#   else
-#     define CYTHON_UNUSED
-#   endif
-# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
-#   define CYTHON_UNUSED __attribute__ ((__unused__))
-# else
-#   define CYTHON_UNUSED
-# endif
-#endif
-#ifndef CYTHON_MAYBE_UNUSED_VAR
-#  if defined(__cplusplus)
-     template<class T> void CYTHON_MAYBE_UNUSED_VAR( const T& ) { }
-#  else
-#    define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x)
-#  endif
-#endif
-#ifndef CYTHON_NCP_UNUSED
-# if CYTHON_COMPILING_IN_CPYTHON
-#  define CYTHON_NCP_UNUSED
-# else
-#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
-# endif
-#endif
 #define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None)
 
 #ifndef CYTHON_INLINE
-  #if defined(__clang__)
-    #define CYTHON_INLINE __inline__ __attribute__ ((__unused__))
-  #elif defined(__GNUC__)
+  #if defined(__GNUC__)
     #define CYTHON_INLINE __inline__
   #elif defined(_MSC_VER)
     #define CYTHON_INLINE __inline
@@ -406,11 +241,6 @@ static CYTHON_INLINE float __PYX_NAN() {
   return value;
 }
 #endif
-#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL)
-#define __Pyx_truncl trunc
-#else
-#define __Pyx_truncl truncl
-#endif
 
 
 #define __PYX_ERR(f_index, lineno, Ln_error) \
@@ -444,6 +274,26 @@ static CYTHON_INLINE float __PYX_NAN() {
 #define CYTHON_WITHOUT_ASSERTIONS
 #endif
 
+#ifndef CYTHON_UNUSED
+# if defined(__GNUC__)
+#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
+#     define CYTHON_UNUSED __attribute__ ((__unused__))
+#   else
+#     define CYTHON_UNUSED
+#   endif
+# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
+#   define CYTHON_UNUSED __attribute__ ((__unused__))
+# else
+#   define CYTHON_UNUSED
+# endif
+#endif
+#ifndef CYTHON_NCP_UNUSED
+# if CYTHON_COMPILING_IN_CPYTHON
+#  define CYTHON_NCP_UNUSED
+# else
+#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
+# endif
+#endif
 typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding;
                 const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry;
 
@@ -521,7 +371,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*);
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x);
 static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*);
 static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t);
-#if CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x))
 #else
 #define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x)
@@ -631,7 +481,7 @@ static const char *__pyx_filename;
 
 
 static const char *__pyx_f[] = {
-  "cutadapt/_qualtrim.pyx",
+  "src/cutadapt/_qualtrim.pyx",
 };
 
 /*--- Type declarations ---*/
@@ -701,7 +551,7 @@ static const char *__pyx_f[] = {
 #define __Pyx_XCLEAR(r)   do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0)
 
 /* PyObjectGetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name) {
     PyTypeObject* tp = Py_TYPE(obj);
     if (likely(tp->tp_getattro))
@@ -855,7 +705,7 @@ static const char __pyx_k_Quality_trimming[] = "\nQuality trimming.\n";
 static const char __pyx_k_cutadapt__qualtrim[] = "cutadapt._qualtrim";
 static const char __pyx_k_nextseq_trim_index[] = "nextseq_trim_index";
 static const char __pyx_k_quality_trim_index[] = "quality_trim_index";
-static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/_qualtrim.pyx";
+static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/src/cutadapt/_qualtrim.pyx";
 static PyObject *__pyx_n_s_G;
 static PyObject *__pyx_n_s_base;
 static PyObject *__pyx_n_s_bases;
@@ -1712,7 +1562,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
                                                               CYTHON_NCP_UNUSED int wraparound,
                                                               CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
     if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
         PyObject *r = PyList_GET_ITEM(o, i);
@@ -1727,7 +1577,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
                                                               CYTHON_NCP_UNUSED int wraparound,
                                                               CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
     if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
         PyObject *r = PyTuple_GET_ITEM(o, i);
@@ -1742,7 +1592,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize
 static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list,
                                                      CYTHON_NCP_UNUSED int wraparound,
                                                      CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
     if (is_list || PyList_CheckExact(o)) {
         Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
         if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
@@ -2118,7 +1968,7 @@ static void __Pyx_AddTraceback(const char *funcname, int c_line,
         0                    /*PyObject *locals*/
     );
     if (!py_frame) goto bad;
-    __Pyx_PyFrame_SetLineNumber(py_frame, py_line);
+    py_frame->f_lineno = py_line;
     PyTraceBack_Here(py_frame);
 bad:
     Py_XDECREF(py_code);
@@ -2156,18 +2006,14 @@ static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) {
             return PyInt_FromLong((long) value);
         } else if (sizeof(int) <= sizeof(unsigned long)) {
             return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
             return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
         }
     } else {
         if (sizeof(int) <= sizeof(long)) {
             return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
             return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
         }
     }
     {
@@ -2246,10 +2092,8 @@ static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
 #endif
             if (sizeof(int) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -2316,10 +2160,8 @@ static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
 #endif
             if (sizeof(int) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -2376,18 +2218,14 @@ static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
             return PyInt_FromLong((long) value);
         } else if (sizeof(long) <= sizeof(unsigned long)) {
             return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
             return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
         }
     } else {
         if (sizeof(long) <= sizeof(long)) {
             return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
             return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
         }
     }
     {
@@ -2466,10 +2304,8 @@ static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {
 #endif
             if (sizeof(long) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -2536,10 +2372,8 @@ static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {
 #endif
             if (sizeof(long) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -2705,9 +2539,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject* x) {
    else return PyObject_IsTrue(x);
 }
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
-#if CYTHON_USE_TYPE_SLOTS
   PyNumberMethods *m;
-#endif
   const char *name = NULL;
   PyObject *res = NULL;
 #if PY_MAJOR_VERSION < 3
@@ -2716,9 +2548,8 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
   if (PyLong_Check(x))
 #endif
     return __Pyx_NewRef(x);
-#if CYTHON_USE_TYPE_SLOTS
   m = Py_TYPE(x)->tp_as_number;
-  #if PY_MAJOR_VERSION < 3
+#if PY_MAJOR_VERSION < 3
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Int(x);
@@ -2727,14 +2558,11 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
     name = "long";
     res = PyNumber_Long(x);
   }
-  #else
+#else
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Long(x);
   }
-  #endif
-#else
-  res = PyNumber_Int(x);
 #endif
   if (res) {
 #if PY_MAJOR_VERSION < 3
diff --git a/cutadapt/_qualtrim.pyx b/src/cutadapt/_qualtrim.pyx
similarity index 100%
rename from cutadapt/_qualtrim.pyx
rename to src/cutadapt/_qualtrim.pyx
diff --git a/cutadapt/_seqio.c b/src/cutadapt/_seqio.c
similarity index 59%
rename from cutadapt/_seqio.c
rename to src/cutadapt/_seqio.c
index 923bc46..e96c115 100644
--- a/cutadapt/_seqio.c
+++ b/src/cutadapt/_seqio.c
@@ -1,9 +1,8 @@
-/* Generated by Cython 0.25.2 */
+/* Generated by Cython 0.24 */
 
 /* BEGIN: Cython Metadata
 {
-    "distutils": {},
-    "module_name": "cutadapt._seqio"
+    "distutils": {}
 }
 END: Cython Metadata */
 
@@ -14,7 +13,7 @@ END: Cython Metadata */
 #elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03020000)
     #error Cython requires Python 2.6+ or Python 3.2+.
 #else
-#define CYTHON_ABI "0_25_2"
+#define CYTHON_ABI "0_24"
 #include <stddef.h>
 #ifndef offsetof
   #define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
@@ -36,11 +35,6 @@ END: Cython Metadata */
 #ifndef DL_EXPORT
   #define DL_EXPORT(t) t
 #endif
-#ifndef HAVE_LONG_LONG
-  #if PY_VERSION_HEX >= 0x03030000 || (PY_MAJOR_VERSION == 2 && PY_VERSION_HEX >= 0x02070000)
-    #define HAVE_LONG_LONG
-  #endif
-#endif
 #ifndef PY_LONG_LONG
   #define PY_LONG_LONG LONG_LONG
 #endif
@@ -49,110 +43,13 @@ END: Cython Metadata */
 #endif
 #ifdef PYPY_VERSION
   #define CYTHON_COMPILING_IN_PYPY 1
-  #define CYTHON_COMPILING_IN_PYSTON 0
-  #define CYTHON_COMPILING_IN_CPYTHON 0
-  #undef CYTHON_USE_TYPE_SLOTS
-  #define CYTHON_USE_TYPE_SLOTS 0
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_INTERNALS
-  #define CYTHON_USE_UNICODE_INTERNALS 0
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #undef CYTHON_AVOID_BORROWED_REFS
-  #define CYTHON_AVOID_BORROWED_REFS 1
-  #undef CYTHON_ASSUME_SAFE_MACROS
-  #define CYTHON_ASSUME_SAFE_MACROS 0
-  #undef CYTHON_UNPACK_METHODS
-  #define CYTHON_UNPACK_METHODS 0
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
-#elif defined(PYSTON_VERSION)
-  #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 1
   #define CYTHON_COMPILING_IN_CPYTHON 0
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #undef CYTHON_USE_ASYNC_SLOTS
-  #define CYTHON_USE_ASYNC_SLOTS 0
-  #undef CYTHON_USE_PYLIST_INTERNALS
-  #define CYTHON_USE_PYLIST_INTERNALS 0
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #undef CYTHON_USE_UNICODE_WRITER
-  #define CYTHON_USE_UNICODE_WRITER 0
-  #undef CYTHON_USE_PYLONG_INTERNALS
-  #define CYTHON_USE_PYLONG_INTERNALS 0
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #undef CYTHON_FAST_THREAD_STATE
-  #define CYTHON_FAST_THREAD_STATE 0
-  #undef CYTHON_FAST_PYCALL
-  #define CYTHON_FAST_PYCALL 0
 #else
   #define CYTHON_COMPILING_IN_PYPY 0
-  #define CYTHON_COMPILING_IN_PYSTON 0
   #define CYTHON_COMPILING_IN_CPYTHON 1
-  #ifndef CYTHON_USE_TYPE_SLOTS
-    #define CYTHON_USE_TYPE_SLOTS 1
-  #endif
-  #if PY_MAJOR_VERSION < 3
-    #undef CYTHON_USE_ASYNC_SLOTS
-    #define CYTHON_USE_ASYNC_SLOTS 0
-  #elif !defined(CYTHON_USE_ASYNC_SLOTS)
-    #define CYTHON_USE_ASYNC_SLOTS 1
-  #endif
-  #if PY_VERSION_HEX < 0x02070000
-    #undef CYTHON_USE_PYLONG_INTERNALS
-    #define CYTHON_USE_PYLONG_INTERNALS 0
-  #elif !defined(CYTHON_USE_PYLONG_INTERNALS)
-    #define CYTHON_USE_PYLONG_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_PYLIST_INTERNALS
-    #define CYTHON_USE_PYLIST_INTERNALS 1
-  #endif
-  #ifndef CYTHON_USE_UNICODE_INTERNALS
-    #define CYTHON_USE_UNICODE_INTERNALS 1
-  #endif
-  #if PY_VERSION_HEX < 0x030300F0
-    #undef CYTHON_USE_UNICODE_WRITER
-    #define CYTHON_USE_UNICODE_WRITER 0
-  #elif !defined(CYTHON_USE_UNICODE_WRITER)
-    #define CYTHON_USE_UNICODE_WRITER 1
-  #endif
-  #ifndef CYTHON_AVOID_BORROWED_REFS
-    #define CYTHON_AVOID_BORROWED_REFS 0
-  #endif
-  #ifndef CYTHON_ASSUME_SAFE_MACROS
-    #define CYTHON_ASSUME_SAFE_MACROS 1
-  #endif
-  #ifndef CYTHON_UNPACK_METHODS
-    #define CYTHON_UNPACK_METHODS 1
-  #endif
-  #ifndef CYTHON_FAST_THREAD_STATE
-    #define CYTHON_FAST_THREAD_STATE 1
-  #endif
-  #ifndef CYTHON_FAST_PYCALL
-    #define CYTHON_FAST_PYCALL 1
-  #endif
 #endif
-#if !defined(CYTHON_FAST_PYCCALL)
-#define CYTHON_FAST_PYCCALL  (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1)
+#if !defined(CYTHON_USE_PYLONG_INTERNALS) && CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x02070000
+  #define CYTHON_USE_PYLONG_INTERNALS 1
 #endif
 #if CYTHON_USE_PYLONG_INTERNALS
   #include "longintrepr.h"
@@ -188,44 +85,24 @@ END: Cython Metadata */
 #ifndef Py_TPFLAGS_HAVE_FINALIZE
   #define Py_TPFLAGS_HAVE_FINALIZE 0
 #endif
-#ifndef METH_FASTCALL
-  #define METH_FASTCALL 0x80
-  typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject **args,
-                                              Py_ssize_t nargs, PyObject *kwnames);
-#else
-  #define __Pyx_PyCFunctionFast _PyCFunctionFast
-#endif
-#if CYTHON_FAST_PYCCALL
-#define __Pyx_PyFastCFunction_Check(func)\
-    ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)))))
-#else
-#define __Pyx_PyFastCFunction_Check(func) 0
-#endif
 #if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
   #define CYTHON_PEP393_ENABLED 1
   #define __Pyx_PyUnicode_READY(op)       (likely(PyUnicode_IS_READY(op)) ?\
                                               0 : _PyUnicode_Ready((PyObject *)(op)))
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_LENGTH(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i)
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   PyUnicode_MAX_CHAR_VALUE(u)
   #define __Pyx_PyUnicode_KIND(u)         PyUnicode_KIND(u)
   #define __Pyx_PyUnicode_DATA(u)         PyUnicode_DATA(u)
   #define __Pyx_PyUnicode_READ(k, d, i)   PyUnicode_READ(k, d, i)
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  PyUnicode_WRITE(k, d, i, ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u)))
 #else
   #define CYTHON_PEP393_ENABLED 0
-  #define PyUnicode_1BYTE_KIND  1
-  #define PyUnicode_2BYTE_KIND  2
-  #define PyUnicode_4BYTE_KIND  4
   #define __Pyx_PyUnicode_READY(op)       (0)
   #define __Pyx_PyUnicode_GET_LENGTH(u)   PyUnicode_GET_SIZE(u)
   #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i]))
-  #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u)   ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111)
   #define __Pyx_PyUnicode_KIND(u)         (sizeof(Py_UNICODE))
   #define __Pyx_PyUnicode_DATA(u)         ((void*)PyUnicode_AS_UNICODE(u))
   #define __Pyx_PyUnicode_READ(k, d, i)   ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i]))
-  #define __Pyx_PyUnicode_WRITE(k, d, i, ch)  (((void)(k)), ((Py_UNICODE*)d)[i] = ch)
   #define __Pyx_PyUnicode_IS_TRUE(u)      (0 != PyUnicode_GET_SIZE(u))
 #endif
 #if CYTHON_COMPILING_IN_PYPY
@@ -239,9 +116,6 @@ END: Cython Metadata */
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains)
   #define PyUnicode_Contains(u, s)  PySequence_Contains(u, s)
 #endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check)
-  #define PyByteArray_Check(obj)  PyObject_TypeCheck(obj, &PyByteArray_Type)
-#endif
 #if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format)
   #define PyObject_Format(obj, fmt)  PyObject_CallMethod(obj, "__format__", "O", fmt)
 #endif
@@ -250,13 +124,6 @@ END: Cython Metadata */
   #define PyObject_Free(p)     PyMem_Free(p)
   #define PyObject_Realloc(p)  PyMem_Realloc(p)
 #endif
-#if CYTHON_COMPILING_IN_PYSTON
-  #define __Pyx_PyCode_HasFreeVars(co)  PyCode_HasFreeVars(co)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno)
-#else
-  #define __Pyx_PyCode_HasFreeVars(co)  (PyCode_GetNumFree(co) > 0)
-  #define __Pyx_PyFrame_SetLineNumber(frame, lineno)  (frame)->f_lineno = (lineno)
-#endif
 #define __Pyx_PyString_FormatSafe(a, b)   ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b))
 #define __Pyx_PyUnicode_FormatSafe(a, b)  ((unlikely((a) == Py_None)) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b))
 #if PY_MAJOR_VERSION >= 3
@@ -285,7 +152,6 @@ END: Cython Metadata */
   #define PySet_CheckExact(obj)        (Py_TYPE(obj) == &PySet_Type)
 #endif
 #define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type)
-#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception)
 #if PY_MAJOR_VERSION >= 3
   #define PyIntObject                  PyLongObject
   #define PyInt_Type                   PyLong_Type
@@ -324,20 +190,18 @@ END: Cython Metadata */
 #else
   #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
 #endif
-#if CYTHON_USE_ASYNC_SLOTS
-  #if PY_VERSION_HEX >= 0x030500B1
-    #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
-    #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
-  #else
-    typedef struct {
-        unaryfunc am_await;
-        unaryfunc am_aiter;
-        unaryfunc am_anext;
-    } __Pyx_PyAsyncMethodsStruct;
-    #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
-  #endif
+#if PY_VERSION_HEX >= 0x030500B1
+#define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
+#define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
+#elif CYTHON_COMPILING_IN_CPYTHON && PY_MAJOR_VERSION >= 3
+typedef struct {
+    unaryfunc am_await;
+    unaryfunc am_aiter;
+    unaryfunc am_anext;
+} __Pyx_PyAsyncMethodsStruct;
+#define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
 #else
-  #define __Pyx_PyType_AsAsync(obj) NULL
+#define __Pyx_PyType_AsAsync(obj) NULL
 #endif
 #ifndef CYTHON_RESTRICT
   #if defined(__GNUC__)
@@ -350,39 +214,10 @@ END: Cython Metadata */
     #define CYTHON_RESTRICT
   #endif
 #endif
-#ifndef CYTHON_UNUSED
-# if defined(__GNUC__)
-#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
-#     define CYTHON_UNUSED __attribute__ ((__unused__))
-#   else
-#     define CYTHON_UNUSED
-#   endif
-# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
-#   define CYTHON_UNUSED __attribute__ ((__unused__))
-# else
-#   define CYTHON_UNUSED
-# endif
-#endif
-#ifndef CYTHON_MAYBE_UNUSED_VAR
-#  if defined(__cplusplus)
-     template<class T> void CYTHON_MAYBE_UNUSED_VAR( const T& ) { }
-#  else
-#    define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x)
-#  endif
-#endif
-#ifndef CYTHON_NCP_UNUSED
-# if CYTHON_COMPILING_IN_CPYTHON
-#  define CYTHON_NCP_UNUSED
-# else
-#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
-# endif
-#endif
 #define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None)
 
 #ifndef CYTHON_INLINE
-  #if defined(__clang__)
-    #define CYTHON_INLINE __inline__ __attribute__ ((__unused__))
-  #elif defined(__GNUC__)
+  #if defined(__GNUC__)
     #define CYTHON_INLINE __inline__
   #elif defined(_MSC_VER)
     #define CYTHON_INLINE __inline
@@ -406,11 +241,6 @@ static CYTHON_INLINE float __PYX_NAN() {
   return value;
 }
 #endif
-#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL)
-#define __Pyx_truncl trunc
-#else
-#define __Pyx_truncl truncl
-#endif
 
 
 #define __PYX_ERR(f_index, lineno, Ln_error) \
@@ -444,6 +274,26 @@ static CYTHON_INLINE float __PYX_NAN() {
 #define CYTHON_WITHOUT_ASSERTIONS
 #endif
 
+#ifndef CYTHON_UNUSED
+# if defined(__GNUC__)
+#   if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
+#     define CYTHON_UNUSED __attribute__ ((__unused__))
+#   else
+#     define CYTHON_UNUSED
+#   endif
+# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
+#   define CYTHON_UNUSED __attribute__ ((__unused__))
+# else
+#   define CYTHON_UNUSED
+# endif
+#endif
+#ifndef CYTHON_NCP_UNUSED
+# if CYTHON_COMPILING_IN_CPYTHON
+#  define CYTHON_NCP_UNUSED
+# else
+#  define CYTHON_NCP_UNUSED CYTHON_UNUSED
+# endif
+#endif
 typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding;
                 const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry;
 
@@ -521,7 +371,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*);
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x);
 static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*);
 static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t);
-#if CYTHON_ASSUME_SAFE_MACROS
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x))
 #else
 #define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x)
@@ -631,7 +481,7 @@ static const char *__pyx_filename;
 
 
 static const char *__pyx_f[] = {
-  "cutadapt/_seqio.pyx",
+  "src/cutadapt/_seqio.pyx",
 };
 
 /*--- Type declarations ---*/
@@ -639,7 +489,27 @@ struct __pyx_obj_8cutadapt_6_seqio_Sequence;
 struct __pyx_obj_8cutadapt_6_seqio___pyx_scope_struct____iter__;
 struct __pyx_defaults;
 typedef struct __pyx_defaults __pyx_defaults;
+struct __pyx_defaults1;
+typedef struct __pyx_defaults1 __pyx_defaults1;
+struct __pyx_defaults2;
+typedef struct __pyx_defaults2 __pyx_defaults2;
+struct __pyx_defaults3;
+typedef struct __pyx_defaults3 __pyx_defaults3;
+struct __pyx_defaults4;
+typedef struct __pyx_defaults4 __pyx_defaults4;
 struct __pyx_defaults {
+  Py_ssize_t __pyx_arg_end;
+};
+struct __pyx_defaults1 {
+  Py_ssize_t __pyx_arg_end;
+};
+struct __pyx_defaults2 {
+  Py_ssize_t __pyx_arg_end;
+};
+struct __pyx_defaults3 {
+  Py_ssize_t __pyx_arg_end;
+};
+struct __pyx_defaults4 {
   PyObject *__pyx_arg_sequence_class;
 };
 
@@ -737,7 +607,7 @@ struct __pyx_obj_8cutadapt_6_seqio___pyx_scope_struct____iter__ {
 #define __Pyx_XCLEAR(r)   do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0)
 
 /* PyObjectGetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name) {
     PyTypeObject* tp = Py_TYPE(obj);
     if (likely(tp->tp_getattro))
@@ -767,29 +637,53 @@ static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\
     PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args,\
     const char* function_name);
 
-/* ArgTypeTest.proto */
-static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed,
-    const char *name, int exact);
-
-/* GetModuleGlobalName.proto */
-static CYTHON_INLINE PyObject *__Pyx_GetModuleGlobalName(PyObject *name);
-
-/* PyCFunctionFastCall.proto */
-#if CYTHON_FAST_PYCCALL
-static CYTHON_INLINE PyObject *__Pyx_PyCFunction_FastCall(PyObject *func, PyObject **args, Py_ssize_t nargs);
-#else
-#define __Pyx_PyCFunction_FastCall(func, args, nargs)  (assert(0), NULL)
-#endif
+/* GetItemInt.proto */
+#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
+    __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) :\
+    (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) :\
+               __Pyx_GetItemInt_Generic(o, to_py_func(i))))
+#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
+    __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
+    (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck);
+#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
+    __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
+    (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL))
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck);
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j);
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
+                                                     int is_list, int wraparound, int boundscheck);
 
-/* PyFunctionFastCall.proto */
-#if CYTHON_FAST_PYCALL
-#define __Pyx_PyFunction_FastCall(func, args, nargs)\
-    __Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL)
-#if 1 || PY_VERSION_HEX < 0x030600B1
-static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs);
+/* PyDictContains.proto */
+static CYTHON_INLINE int __Pyx_PyDict_ContainsTF(PyObject* item, PyObject* dict, int eq) {
+    int result = PyDict_Contains(dict, item);
+    return unlikely(result < 0) ? result : (result == (eq == Py_EQ));
+}
+
+/* DictGetItem.proto */
+#if PY_MAJOR_VERSION >= 3 && !CYTHON_COMPILING_IN_PYPY
+static PyObject *__Pyx_PyDict_GetItem(PyObject *d, PyObject* key) {
+    PyObject *value;
+    value = PyDict_GetItemWithError(d, key);
+    if (unlikely(!value)) {
+        if (!PyErr_Occurred()) {
+            PyObject* args = PyTuple_Pack(1, key);
+            if (likely(args))
+                PyErr_SetObject(PyExc_KeyError, args);
+            Py_XDECREF(args);
+        }
+        return NULL;
+    }
+    Py_INCREF(value);
+    return value;
+}
 #else
-#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs)
-#endif
+    #define __Pyx_PyDict_GetItem(d, key) PyObject_GetItem(d, key)
 #endif
 
 /* PyObjectCall.proto */
@@ -799,16 +693,8 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg
 #define __Pyx_PyObject_Call(func, arg, kw) PyObject_Call(func, arg, kw)
 #endif
 
-/* PyObjectCallMethO.proto */
-#if CYTHON_COMPILING_IN_CPYTHON
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg);
-#endif
-
-/* PyObjectCallOneArg.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg);
-
 /* PyThreadStateGet.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_PyThreadState_declare  PyThreadState *__pyx_tstate;
 #define __Pyx_PyThreadState_assign  __pyx_tstate = PyThreadState_GET();
 #else
@@ -817,7 +703,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObjec
 #endif
 
 /* PyErrFetchRestore.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_ErrRestoreWithState(type, value, tb)  __Pyx_ErrRestoreInState(PyThreadState_GET(), type, value, tb)
 #define __Pyx_ErrFetchWithState(type, value, tb)    __Pyx_ErrFetchInState(PyThreadState_GET(), type, value, tb)
 #define __Pyx_ErrRestore(type, value, tb)  __Pyx_ErrRestoreInState(__pyx_tstate, type, value, tb)
@@ -834,6 +720,24 @@ static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject
 /* RaiseException.proto */
 static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause);
 
+/* SetItemInt.proto */
+#define __Pyx_SetItemInt(o, i, v, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
+    __Pyx_SetItemInt_Fast(o, (Py_ssize_t)i, v, is_list, wraparound, boundscheck) :\
+    (is_list ? (PyErr_SetString(PyExc_IndexError, "list assignment index out of range"), -1) :\
+               __Pyx_SetItemInt_Generic(o, to_py_func(i), v)))
+static CYTHON_INLINE int __Pyx_SetItemInt_Generic(PyObject *o, PyObject *j, PyObject *v);
+static CYTHON_INLINE int __Pyx_SetItemInt_Fast(PyObject *o, Py_ssize_t i, PyObject *v,
+                                               int is_list, int wraparound, int boundscheck);
+
+/* IterFinish.proto */
+static CYTHON_INLINE int __Pyx_IterFinish(void);
+
+/* PyObjectCallMethO.proto */
+#if CYTHON_COMPILING_IN_CPYTHON
+static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg);
+#endif
+
 /* PyObjectCallNoArg.proto */
 #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func);
@@ -841,8 +745,63 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func);
 #define __Pyx_PyObject_CallNoArg(func) __Pyx_PyObject_Call(func, __pyx_empty_tuple, NULL)
 #endif
 
+/* PyObjectCallOneArg.proto */
+static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg);
+
+/* PyObjectCallMethod0.proto */
+static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name);
+
+/* RaiseNeedMoreValuesToUnpack.proto */
+static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index);
+
+/* RaiseTooManyValuesToUnpack.proto */
+static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected);
+
+/* UnpackItemEndCheck.proto */
+static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected);
+
+/* RaiseNoneIterError.proto */
+static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void);
+
+/* UnpackTupleError.proto */
+static void __Pyx_UnpackTupleError(PyObject *, Py_ssize_t index);
+
+/* UnpackTuple2.proto */
+static CYTHON_INLINE int __Pyx_unpack_tuple2(PyObject* tuple, PyObject** value1, PyObject** value2,
+                                             int is_tuple, int has_known_size, int decref_tuple);
+
+/* dict_iter.proto */
+static CYTHON_INLINE PyObject* __Pyx_dict_iterator(PyObject* dict, int is_dict, PyObject* method_name,
+                                                   Py_ssize_t* p_orig_length, int* p_is_dict);
+static CYTHON_INLINE int __Pyx_dict_iter_next(PyObject* dict_or_iter, Py_ssize_t orig_length, Py_ssize_t* ppos,
+                                              PyObject** pkey, PyObject** pvalue, PyObject** pitem, int is_dict);
+
+/* ListAppend.proto */
+#if CYTHON_COMPILING_IN_CPYTHON
+static CYTHON_INLINE int __Pyx_PyList_Append(PyObject* list, PyObject* x) {
+    PyListObject* L = (PyListObject*) list;
+    Py_ssize_t len = Py_SIZE(list);
+    if (likely(L->allocated > len) & likely(len > (L->allocated >> 1))) {
+        Py_INCREF(x);
+        PyList_SET_ITEM(list, len, x);
+        Py_SIZE(list) = len+1;
+        return 0;
+    }
+    return PyList_Append(list, x);
+}
+#else
+#define __Pyx_PyList_Append(L,x) PyList_Append(L,x)
+#endif
+
+/* ArgTypeTest.proto */
+static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed,
+    const char *name, int exact);
+
+/* GetModuleGlobalName.proto */
+static CYTHON_INLINE PyObject *__Pyx_GetModuleGlobalName(PyObject *name);
+
 /* PyObjectSetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_PyObject_DelAttrStr(o,n) __Pyx_PyObject_SetAttrStr(o,n,NULL)
 static CYTHON_INLINE int __Pyx_PyObject_SetAttrStr(PyObject* obj, PyObject* attr_name, PyObject* value) {
     PyTypeObject* tp = Py_TYPE(obj);
@@ -863,28 +822,6 @@ static CYTHON_INLINE int __Pyx_PyObject_SetAttrStr(PyObject* obj, PyObject* attr
 #define __Pyx_PyIter_Next(obj) __Pyx_PyIter_Next2(obj, NULL)
 static CYTHON_INLINE PyObject *__Pyx_PyIter_Next2(PyObject *, PyObject *);
 
-/* GetItemInt.proto */
-#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
-    __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) :\
-    (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) :\
-               __Pyx_GetItemInt_Generic(o, to_py_func(i))))
-#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
-    __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
-    (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck);
-#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
-    __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
-    (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck);
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j);
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
-                                                     int is_list, int wraparound, int boundscheck);
-
 /* IncludeStringH.proto */
 #include <string.h>
 
@@ -927,9 +864,6 @@ static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level);
 /* ImportFrom.proto */
 static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name);
 
-/* CalculateMetaclass.proto */
-static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases);
-
 /* FetchCommonType.proto */
 static PyTypeObject* __Pyx_FetchCommonType(PyTypeObject* type);
 
@@ -987,6 +921,28 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *m,
                                                               PyObject *dict);
 static int __pyx_CyFunction_init(void);
 
+/* FusedFunction.proto */
+typedef struct {
+    __pyx_CyFunctionObject func;
+    PyObject *__signatures__;
+    PyObject *type;
+    PyObject *self;
+} __pyx_FusedFunctionObject;
+#define __pyx_FusedFunction_NewEx(ml, flags, qualname, self, module, globals, code)\
+        __pyx_FusedFunction_New(__pyx_FusedFunctionType, ml, flags, qualname, self, module, globals, code)
+static PyObject *__pyx_FusedFunction_New(PyTypeObject *type,
+                                         PyMethodDef *ml, int flags,
+                                         PyObject *qualname, PyObject *self,
+                                         PyObject *module, PyObject *globals,
+                                         PyObject *code);
+static int __pyx_FusedFunction_clear(__pyx_FusedFunctionObject *self);
+static PyTypeObject *__pyx_FusedFunctionType = NULL;
+static int __pyx_FusedFunction_init(void);
+#define __Pyx_FusedFunction_USED
+
+/* CalculateMetaclass.proto */
+static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases);
+
 /* Py3ClassCreate.proto */
 static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname,
                                            PyObject *mkw, PyObject *modname, PyObject *doc);
@@ -1022,7 +978,7 @@ static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *);
 static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *);
 
 /* SwapException.proto */
-#if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 #define __Pyx_ExceptionSwap(type, value, tb)  __Pyx__ExceptionSwap(__pyx_tstate, type, value, tb)
 static CYTHON_INLINE void __Pyx__ExceptionSwap(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
 #else
@@ -1046,13 +1002,11 @@ typedef struct {
     PyObject *yieldfrom;
     PyObject *gi_name;
     PyObject *gi_qualname;
-    PyObject *gi_modulename;
     int resume_label;
     char is_running;
 } __pyx_CoroutineObject;
-static __pyx_CoroutineObject *__Pyx__Coroutine_New(
-    PyTypeObject *type, __pyx_coroutine_body_t body, PyObject *closure,
-    PyObject *name, PyObject *qualname, PyObject *module_name);
+static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject *type, __pyx_coroutine_body_t body,
+                                                   PyObject *closure, PyObject *name, PyObject *qualname);
 static int __Pyx_Coroutine_clear(PyObject *self);
 #if 1 || PY_VERSION_HEX < 0x030300B0
 static int __Pyx_PyGen_FetchStopIterationValue(PyObject **pvalue);
@@ -1070,8 +1024,8 @@ static int __Pyx_patch_abc(void);
 #define __Pyx_Generator_USED
 static PyTypeObject *__pyx_GeneratorType = 0;
 #define __Pyx_Generator_CheckExact(obj) (Py_TYPE(obj) == __pyx_GeneratorType)
-#define __Pyx_Generator_New(body, closure, name, qualname, module_name)\
-    __Pyx__Coroutine_New(__pyx_GeneratorType, body, closure, name, qualname, module_name)
+#define __Pyx_Generator_New(body, closure, name, qualname)\
+    __Pyx__Coroutine_New(__pyx_GeneratorType, body, closure, name, qualname)
 static PyObject *__Pyx_Generator_Next(PyObject *self);
 static int __pyx_Generator_init(void);
 
@@ -1082,6 +1036,8 @@ static int __Pyx_check_binary_version(void);
 static int __Pyx_InitStrings(__Pyx_StringTabEntry *t);
 
 
+/* Module declarations from 'cython' */
+
 /* Module declarations from 'cutadapt._seqio' */
 static PyTypeObject *__pyx_ptype_8cutadapt_6_seqio_Sequence = 0;
 static PyTypeObject *__pyx_ptype_8cutadapt_6_seqio___pyx_scope_struct____iter__ = 0;
@@ -1089,31 +1045,52 @@ static PyTypeObject *__pyx_ptype_8cutadapt_6_seqio___pyx_scope_struct____iter__
 int __pyx_module_is_main_cutadapt___seqio = 0;
 
 /* Implementation of 'cutadapt._seqio' */
+static PyObject *__pyx_builtin_TypeError;
+static PyObject *__pyx_builtin_zip;
 static PyObject *__pyx_builtin_NotImplementedError;
 static PyObject *__pyx_builtin_super;
-static const char __pyx_k_[] = "";
+static const char __pyx_k_[] = "()";
 static const char __pyx_k_i[] = "i";
-static const char __pyx_k__2[] = "@";
-static const char __pyx_k__3[] = "\r\n";
-static const char __pyx_k__4[] = "+\n";
-static const char __pyx_k__5[] = "+";
+static const char __pyx_k__3[] = "|";
 static const char __pyx_k_it[] = "it";
+static const char __pyx_k__16[] = "";
+static const char __pyx_k__17[] = "@";
+static const char __pyx_k__18[] = "\r\n";
+static const char __pyx_k__19[] = "+\n";
+static const char __pyx_k__20[] = "+";
+static const char __pyx_k_buf[] = "buf";
 static const char __pyx_k_doc[] = "__doc__";
+static const char __pyx_k_end[] = "end";
+static const char __pyx_k_pos[] = "pos";
+static const char __pyx_k_zip[] = "zip";
 static const char __pyx_k_args[] = "args";
+static const char __pyx_k_buf1[] = "buf1";
+static const char __pyx_k_buf2[] = "buf2";
+static const char __pyx_k_data[] = "data";
+static const char __pyx_k_end1[] = "end1";
+static const char __pyx_k_end2[] = "end2";
 static const char __pyx_k_file[] = "file";
+static const char __pyx_k_head[] = "head";
 static const char __pyx_k_init[] = "__init__";
 static const char __pyx_k_iter[] = "__iter__";
 static const char __pyx_k_line[] = "line";
 static const char __pyx_k_main[] = "__main__";
 static const char __pyx_k_name[] = "name";
+static const char __pyx_k_pos1[] = "pos1";
+static const char __pyx_k_pos2[] = "pos2";
 static const char __pyx_k_self[] = "self";
 static const char __pyx_k_send[] = "send";
 static const char __pyx_k_test[] = "__test__";
+static const char __pyx_k_bytes[] = "bytes";
 static const char __pyx_k_class[] = "__class__";
 static const char __pyx_k_close[] = "close";
+static const char __pyx_k_data1[] = "data1";
+static const char __pyx_k_data2[] = "data2";
+static const char __pyx_k_lines[] = "lines";
 static const char __pyx_k_match[] = "match";
 static const char __pyx_k_name2[] = "name2";
 static const char __pyx_k_seqio[] = "seqio";
+static const char __pyx_k_split[] = "split";
 static const char __pyx_k_strip[] = "strip";
 static const char __pyx_k_super[] = "super";
 static const char __pyx_k_throw[] = "throw";
@@ -1121,77 +1098,126 @@ static const char __pyx_k_xopen[] = "xopen";
 static const char __pyx_k_file_2[] = "_file";
 static const char __pyx_k_format[] = "format";
 static const char __pyx_k_import[] = "__import__";
+static const char __pyx_k_kwargs[] = "kwargs";
+static const char __pyx_k_length[] = "length";
 static const char __pyx_k_module[] = "__module__";
+static const char __pyx_k_name_2[] = "__name__";
 static const char __pyx_k_rstrip[] = "rstrip";
 static const char __pyx_k_prepare[] = "__prepare__";
 static const char __pyx_k_shorten[] = "_shorten";
+static const char __pyx_k_defaults[] = "defaults";
 static const char __pyx_k_qualname[] = "__qualname__";
 static const char __pyx_k_sequence[] = "sequence";
+static const char __pyx_k_TypeError[] = "TypeError";
+static const char __pyx_k_bytearray[] = "bytearray";
 static const char __pyx_k_metaclass[] = "__metaclass__";
 static const char __pyx_k_qualities[] = "qualities";
+static const char __pyx_k_fastq_head[] = "fastq_head";
+static const char __pyx_k_linebreaks[] = "linebreaks";
+static const char __pyx_k_signatures[] = "signatures";
 static const char __pyx_k_FastqReader[] = "FastqReader";
 static const char __pyx_k_FormatError[] = "FormatError";
+static const char __pyx_k_record_start[] = "record_start";
 static const char __pyx_k_qualities_0_r[] = ", qualities={0!r}";
+static const char __pyx_k_record_start1[] = "record_start1";
+static const char __pyx_k_record_start2[] = "record_start2";
 static const char __pyx_k_second_header[] = "second_header";
 static const char __pyx_k_SequenceReader[] = "SequenceReader";
 static const char __pyx_k_sequence_class[] = "sequence_class";
 static const char __pyx_k_cutadapt__seqio[] = "cutadapt._seqio";
+static const char __pyx_k_linebreaks_seen[] = "linebreaks_seen";
+static const char __pyx_k_two_fastq_heads[] = "two_fastq_heads";
 static const char __pyx_k_FastqReader___init[] = "FastqReader.__init__";
 static const char __pyx_k_FastqReader___iter[] = "FastqReader.__iter__";
 static const char __pyx_k_delivers_qualities[] = "delivers_qualities";
 static const char __pyx_k_NotImplementedError[] = "NotImplementedError";
+static const char __pyx_k_No_matching_signature_found[] = "No matching signature found";
 static const char __pyx_k_FASTQ_file_ended_prematurely[] = "FASTQ file ended prematurely";
+static const char __pyx_k_Expected_at_least_d_arguments[] = "Expected at least %d arguments";
 static const char __pyx_k_Sequence_name_0_r_sequence_1_r[] = "<Sequence(name={0!r}, sequence={1!r}{2})>";
 static const char __pyx_k_At_line_0_Sequence_descriptions[] = "At line {0}: Sequence descriptions in the FASTQ file don't match ({1!r} != {2!r}).\nThe second sequence description must be either empty or equal to the first description.";
 static const char __pyx_k_Reader_for_FASTQ_files_Does_not[] = "\n\tReader for FASTQ files. Does not support multi-line FASTQ files.\n\t";
-static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/_seqio.pyx";
+static const char __pyx_k_home_marcel_scm_cutadapt_cutada[] = "/home/marcel/scm/cutadapt/cutadapt/src/cutadapt/_seqio.pyx";
+static const char __pyx_k_Function_call_with_ambiguous_arg[] = "Function call with ambiguous argument types";
 static const char __pyx_k_In_read_named_0_r_length_of_qual[] = "In read named {0!r}: length of quality sequence ({1}) and length of read ({2}) do not match";
 static const char __pyx_k_Line_0_in_FASTQ_file_is_expected[] = "Line {0} in FASTQ file is expected to start with '@', but found {1!r}";
 static const char __pyx_k_Line_0_in_FASTQ_file_is_expected_2[] = "Line {0} in FASTQ file is expected to start with '+', but found {1!r}";
 static PyObject *__pyx_kp_s_;
 static PyObject *__pyx_kp_s_At_line_0_Sequence_descriptions;
+static PyObject *__pyx_kp_s_Expected_at_least_d_arguments;
 static PyObject *__pyx_kp_s_FASTQ_file_ended_prematurely;
 static PyObject *__pyx_n_s_FastqReader;
 static PyObject *__pyx_n_s_FastqReader___init;
 static PyObject *__pyx_n_s_FastqReader___iter;
 static PyObject *__pyx_n_s_FormatError;
+static PyObject *__pyx_kp_s_Function_call_with_ambiguous_arg;
 static PyObject *__pyx_kp_s_In_read_named_0_r_length_of_qual;
 static PyObject *__pyx_kp_s_Line_0_in_FASTQ_file_is_expected;
 static PyObject *__pyx_kp_s_Line_0_in_FASTQ_file_is_expected_2;
+static PyObject *__pyx_kp_s_No_matching_signature_found;
 static PyObject *__pyx_n_s_NotImplementedError;
 static PyObject *__pyx_kp_s_Reader_for_FASTQ_files_Does_not;
 static PyObject *__pyx_n_s_SequenceReader;
 static PyObject *__pyx_kp_s_Sequence_name_0_r_sequence_1_r;
-static PyObject *__pyx_kp_s__2;
+static PyObject *__pyx_n_s_TypeError;
+static PyObject *__pyx_kp_s__16;
+static PyObject *__pyx_kp_s__17;
+static PyObject *__pyx_kp_s__18;
+static PyObject *__pyx_kp_s__19;
+static PyObject *__pyx_kp_s__20;
 static PyObject *__pyx_kp_s__3;
-static PyObject *__pyx_kp_s__4;
-static PyObject *__pyx_kp_s__5;
 static PyObject *__pyx_n_s_args;
+static PyObject *__pyx_n_s_buf;
+static PyObject *__pyx_n_s_buf1;
+static PyObject *__pyx_n_s_buf2;
+static PyObject *__pyx_n_s_bytearray;
+static PyObject *__pyx_n_s_bytes;
 static PyObject *__pyx_n_s_class;
 static PyObject *__pyx_n_s_close;
 static PyObject *__pyx_n_s_cutadapt__seqio;
+static PyObject *__pyx_n_s_data;
+static PyObject *__pyx_n_s_data1;
+static PyObject *__pyx_n_s_data2;
+static PyObject *__pyx_n_s_defaults;
 static PyObject *__pyx_n_s_delivers_qualities;
 static PyObject *__pyx_n_s_doc;
+static PyObject *__pyx_n_s_end;
+static PyObject *__pyx_n_s_end1;
+static PyObject *__pyx_n_s_end2;
+static PyObject *__pyx_n_s_fastq_head;
 static PyObject *__pyx_n_s_file;
 static PyObject *__pyx_n_s_file_2;
 static PyObject *__pyx_n_s_format;
+static PyObject *__pyx_n_s_head;
 static PyObject *__pyx_kp_s_home_marcel_scm_cutadapt_cutada;
 static PyObject *__pyx_n_s_i;
 static PyObject *__pyx_n_s_import;
 static PyObject *__pyx_n_s_init;
 static PyObject *__pyx_n_s_it;
 static PyObject *__pyx_n_s_iter;
+static PyObject *__pyx_n_s_kwargs;
+static PyObject *__pyx_n_s_length;
 static PyObject *__pyx_n_s_line;
+static PyObject *__pyx_n_s_linebreaks;
+static PyObject *__pyx_n_s_linebreaks_seen;
+static PyObject *__pyx_n_s_lines;
 static PyObject *__pyx_n_s_main;
 static PyObject *__pyx_n_s_match;
 static PyObject *__pyx_n_s_metaclass;
 static PyObject *__pyx_n_s_module;
 static PyObject *__pyx_n_s_name;
 static PyObject *__pyx_n_s_name2;
+static PyObject *__pyx_n_s_name_2;
+static PyObject *__pyx_n_s_pos;
+static PyObject *__pyx_n_s_pos1;
+static PyObject *__pyx_n_s_pos2;
 static PyObject *__pyx_n_s_prepare;
 static PyObject *__pyx_n_s_qualities;
 static PyObject *__pyx_kp_s_qualities_0_r;
 static PyObject *__pyx_n_s_qualname;
+static PyObject *__pyx_n_s_record_start;
+static PyObject *__pyx_n_s_record_start1;
+static PyObject *__pyx_n_s_record_start2;
 static PyObject *__pyx_n_s_rstrip;
 static PyObject *__pyx_n_s_second_header;
 static PyObject *__pyx_n_s_self;
@@ -1200,11 +1226,26 @@ static PyObject *__pyx_n_s_seqio;
 static PyObject *__pyx_n_s_sequence;
 static PyObject *__pyx_n_s_sequence_class;
 static PyObject *__pyx_n_s_shorten;
+static PyObject *__pyx_n_s_signatures;
+static PyObject *__pyx_n_s_split;
 static PyObject *__pyx_n_s_strip;
 static PyObject *__pyx_n_s_super;
 static PyObject *__pyx_n_s_test;
 static PyObject *__pyx_n_s_throw;
+static PyObject *__pyx_n_s_two_fastq_heads;
 static PyObject *__pyx_n_s_xopen;
+static PyObject *__pyx_n_s_zip;
+static PyObject *__pyx_pf_8cutadapt_6_seqio_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_6head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_lines); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_8head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_lines); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_2fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_28__defaults__(CYTHON_UNUSED PyObject *__pyx_self); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_12fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_end); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_30__defaults__(CYTHON_UNUSED PyObject *__pyx_self); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_14fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_end); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_4two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_18two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf1, PyObject *__pyx_v_buf2, Py_ssize_t __pyx_v_end1, Py_ssize_t __pyx_v_end2); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_20two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf1, PyObject *__pyx_v_buf2, Py_ssize_t __pyx_v_end1, Py_ssize_t __pyx_v_end2); /* proto */
 static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self, PyObject *__pyx_v_name, PyObject *__pyx_v_sequence, PyObject *__pyx_v_qualities, int __pyx_v_second_header, PyObject *__pyx_v_match); /* proto */
 static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_2__getitem__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self, PyObject *__pyx_v_key); /* proto */
 static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self); /* proto */
@@ -1225,45 +1266,57 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_13second_header_2__set__(struct
 static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_5match___get__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self); /* proto */
 static int __pyx_pf_8cutadapt_6_seqio_8Sequence_5match_2__set__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self, PyObject *__pyx_v_value); /* proto */
 static int __pyx_pf_8cutadapt_6_seqio_8Sequence_5match_4__del__(struct __pyx_obj_8cutadapt_6_seqio_Sequence *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_8cutadapt_6_seqio___defaults__(CYTHON_UNUSED PyObject *__pyx_self); /* proto */
+static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader_5__defaults__(CYTHON_UNUSED PyObject *__pyx_self); /* proto */
 static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_file, PyObject *__pyx_v_sequence_class); /* proto */
 static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader_2__iter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */
 static PyObject *__pyx_tp_new_8cutadapt_6_seqio_Sequence(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
 static PyObject *__pyx_tp_new_8cutadapt_6_seqio___pyx_scope_struct____iter__(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
+static PyObject *__pyx_k__7;
+static PyObject *__pyx_tuple__2;
+static PyObject *__pyx_tuple__4;
+static PyObject *__pyx_tuple__5;
 static PyObject *__pyx_tuple__6;
-static PyObject *__pyx_tuple__7;
 static PyObject *__pyx_tuple__8;
+static PyObject *__pyx_tuple__9;
 static PyObject *__pyx_tuple__10;
-static PyObject *__pyx_codeobj__9;
-static PyObject *__pyx_codeobj__11;
+static PyObject *__pyx_tuple__11;
+static PyObject *__pyx_tuple__12;
+static PyObject *__pyx_tuple__13;
+static PyObject *__pyx_tuple__14;
+static PyObject *__pyx_tuple__15;
+static PyObject *__pyx_tuple__21;
+static PyObject *__pyx_tuple__22;
+static PyObject *__pyx_tuple__23;
+static PyObject *__pyx_tuple__25;
+static PyObject *__pyx_tuple__27;
+static PyObject *__pyx_tuple__29;
+static PyObject *__pyx_tuple__31;
+static PyObject *__pyx_codeobj__24;
+static PyObject *__pyx_codeobj__26;
+static PyObject *__pyx_codeobj__28;
+static PyObject *__pyx_codeobj__30;
+static PyObject *__pyx_codeobj__32;
 
 
 /* Python wrapper */
-static int __pyx_pw_8cutadapt_6_seqio_8Sequence_1__init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
-static char __pyx_doc_8cutadapt_6_seqio_8Sequence___init__[] = "Set qualities to None if there are no quality values";
-#if CYTHON_COMPILING_IN_CPYTHON
-struct wrapperbase __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence___init__;
-#endif
-static int __pyx_pw_8cutadapt_6_seqio_8Sequence_1__init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
-  PyObject *__pyx_v_name = 0;
-  PyObject *__pyx_v_sequence = 0;
-  PyObject *__pyx_v_qualities = 0;
-  int __pyx_v_second_header;
-  PyObject *__pyx_v_match = 0;
-  int __pyx_r;
+static PyObject *__pyx_pw_8cutadapt_6_seqio_1head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_8cutadapt_6_seqio_head[] = "\n\tSkip forward by a number of lines in the given buffer and return\n\thow many bytes this corresponds to.\n\t";
+static PyMethodDef __pyx_mdef_8cutadapt_6_seqio_1head = {"head", (PyCFunction)__pyx_pw_8cutadapt_6_seqio_1head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_head};
+static PyObject *__pyx_pw_8cutadapt_6_seqio_1head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_signatures = 0;
+  PyObject *__pyx_v_args = 0;
+  PyObject *__pyx_v_kwargs = 0;
+  CYTHON_UNUSED PyObject *__pyx_v_defaults = 0;
+  PyObject *__pyx_r = 0;
   __Pyx_RefNannyDeclarations
-  __Pyx_RefNannySetupContext("__init__ (wrapper)", 0);
+  __Pyx_RefNannySetupContext("__pyx_fused_cpdef (wrapper)", 0);
   {
-    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_name,&__pyx_n_s_sequence,&__pyx_n_s_qualities,&__pyx_n_s_second_header,&__pyx_n_s_match,0};
-    PyObject* values[5] = {0,0,0,0,0};
-    values[2] = ((PyObject*)Py_None);
-
-    values[4] = ((PyObject *)Py_None);
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_signatures,&__pyx_n_s_args,&__pyx_n_s_kwargs,&__pyx_n_s_defaults,0};
+    PyObject* values[4] = {0,0,0,0};
     if (unlikely(__pyx_kwds)) {
       Py_ssize_t kw_args;
       const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
       switch (pos_args) {
-        case  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
         case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
         case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
         case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
@@ -1274,71 +1327,2440 @@ static int __pyx_pw_8cutadapt_6_seqio_8Sequence_1__init__(PyObject *__pyx_v_self
       kw_args = PyDict_Size(__pyx_kwds);
       switch (pos_args) {
         case  0:
-        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_name)) != 0)) kw_args--;
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_signatures)) != 0)) kw_args--;
         else goto __pyx_L5_argtuple_error;
         case  1:
-        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_sequence)) != 0)) kw_args--;
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_args)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 5, 1); __PYX_ERR(0, 24, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 1); __PYX_ERR(0, 20, __pyx_L3_error)
         }
         case  2:
-        if (kw_args > 0) {
-          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_qualities);
-          if (value) { values[2] = value; kw_args--; }
+        if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_kwargs)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 2); __PYX_ERR(0, 20, __pyx_L3_error)
         }
         case  3:
-        if (kw_args > 0) {
-          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_second_header);
-          if (value) { values[3] = value; kw_args--; }
-        }
-        case  4:
-        if (kw_args > 0) {
-          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_match);
-          if (value) { values[4] = value; kw_args--; }
+        if (likely((values[3] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_defaults)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 3); __PYX_ERR(0, 20, __pyx_L3_error)
         }
       }
       if (unlikely(kw_args > 0)) {
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__init__") < 0)) __PYX_ERR(0, 24, __pyx_L3_error)
-      }
-    } else {
-      switch (PyTuple_GET_SIZE(__pyx_args)) {
-        case  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
-        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
-        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
-        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
-        values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
-        break;
-        default: goto __pyx_L5_argtuple_error;
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__pyx_fused_cpdef") < 0)) __PYX_ERR(0, 20, __pyx_L3_error)
       }
-    }
-    __pyx_v_name = ((PyObject*)values[0]);
-    __pyx_v_sequence = ((PyObject*)values[1]);
-    __pyx_v_qualities = ((PyObject*)values[2]);
-    if (values[3]) {
-      __pyx_v_second_header = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_second_header == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 24, __pyx_L3_error)
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
+      goto __pyx_L5_argtuple_error;
     } else {
-
-      __pyx_v_second_header = ((int)0);
-    }
-    __pyx_v_match = values[4];
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+    }
+    __pyx_v_signatures = values[0];
+    __pyx_v_args = values[1];
+    __pyx_v_kwargs = values[2];
+    __pyx_v_defaults = values[3];
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 24, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 20, __pyx_L3_error)
   __pyx_L3_error:;
-  __Pyx_AddTraceback("cutadapt._seqio.Sequence.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
-  return -1;
+  return NULL;
   __pyx_L4_argument_unpacking_done:;
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_name), (&PyString_Type), 1, "name", 1))) __PYX_ERR(0, 24, __pyx_L1_error)
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_sequence), (&PyString_Type), 1, "sequence", 1))) __PYX_ERR(0, 24, __pyx_L1_error)
-  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_qualities), (&PyString_Type), 1, "qualities", 1))) __PYX_ERR(0, 24, __pyx_L1_error)
-  __pyx_r = __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(((struct __pyx_obj_8cutadapt_6_seqio_Sequence *)__pyx_v_self), __pyx_v_name, __pyx_v_sequence, __pyx_v_qualities, __pyx_v_second_header, __pyx_v_match);
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_head(__pyx_self, __pyx_v_signatures, __pyx_v_args, __pyx_v_kwargs, __pyx_v_defaults);
 
   /* function exit code */
-  goto __pyx_L0;
-  __pyx_L1_error:;
-  __pyx_r = -1;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults) {
+  PyObject *__pyx_v_dest_sig = NULL;
+  PyObject *__pyx_v_arg = NULL;
+  PyObject *__pyx_v_candidates = NULL;
+  PyObject *__pyx_v_sig = NULL;
+  int __pyx_v_match_found;
+  PyObject *__pyx_v_src_type = NULL;
+  PyObject *__pyx_v_dst_type = NULL;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  int __pyx_t_2;
+  int __pyx_t_3;
+  Py_ssize_t __pyx_t_4;
+  PyObject *__pyx_t_5 = NULL;
+  Py_ssize_t __pyx_t_6;
+  int __pyx_t_7;
+  int __pyx_t_8;
+  PyObject *__pyx_t_9 = NULL;
+  Py_ssize_t __pyx_t_10;
+  PyObject *(*__pyx_t_11)(PyObject *);
+  PyObject *__pyx_t_12 = NULL;
+  PyObject *__pyx_t_13 = NULL;
+  PyObject *__pyx_t_14 = NULL;
+  PyObject *(*__pyx_t_15)(PyObject *);
+  int __pyx_t_16;
+  __Pyx_RefNannySetupContext("head", 0);
+  __Pyx_INCREF(__pyx_v_kwargs);
+  __pyx_t_1 = PyList_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_INCREF(Py_None);
+  __Pyx_GIVEREF(Py_None);
+  PyList_SET_ITEM(__pyx_t_1, 0, Py_None);
+  __pyx_v_dest_sig = ((PyObject*)__pyx_t_1);
+  __pyx_t_1 = 0;
+  __pyx_t_2 = (__pyx_v_kwargs == Py_None);
+  __pyx_t_3 = (__pyx_t_2 != 0);
+  if (__pyx_t_3) {
+    __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF_SET(__pyx_v_kwargs, __pyx_t_1);
+    __pyx_t_1 = 0;
+  }
+  if (unlikely(__pyx_v_args == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_t_3 = ((0 < __pyx_t_4) != 0);
+  if (__pyx_t_3) {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_GetItemInt_Tuple(((PyObject*)__pyx_v_args), 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  if (unlikely(__pyx_v_kwargs == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  __pyx_t_3 = (__Pyx_PyDict_ContainsTF(__pyx_n_s_buf, ((PyObject*)__pyx_v_kwargs), Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_t_2 = (__pyx_t_3 != 0);
+  if (__pyx_t_2) {
+    if (unlikely(__pyx_v_kwargs == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_kwargs), __pyx_n_s_buf); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  /*else*/ {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+      __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+    __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __pyx_t_1 = PyInt_FromSsize_t(__pyx_t_4); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_5 = __Pyx_PyString_Format(__pyx_kp_s_Expected_at_least_d_arguments, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_5);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_5);
+    __pyx_t_5 = 0;
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  __pyx_L4:;
+  while (1) {
+    __pyx_t_2 = PyBytes_Check(__pyx_v_arg); 
+    __pyx_t_3 = (__pyx_t_2 != 0);
+    if (__pyx_t_3) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytes, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    __pyx_t_3 = PyByteArray_Check(__pyx_v_arg); 
+    __pyx_t_2 = (__pyx_t_3 != 0);
+    if (__pyx_t_2) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytearray, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, Py_None, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+    goto __pyx_L6_break;
+  }
+  __pyx_L6_break:;
+  __pyx_t_5 = PyList_New(0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_v_candidates = ((PyObject*)__pyx_t_5);
+  __pyx_t_5 = 0;
+  __pyx_t_4 = 0;
+  if (unlikely(__pyx_v_signatures == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  __pyx_t_1 = __Pyx_dict_iterator(((PyObject*)__pyx_v_signatures), 1, ((PyObject *)NULL), (&__pyx_t_6), (&__pyx_t_7)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __pyx_t_5 = __pyx_t_1;
+  __pyx_t_1 = 0;
+  while (1) {
+    __pyx_t_8 = __Pyx_dict_iter_next(__pyx_t_5, __pyx_t_6, &__pyx_t_4, &__pyx_t_1, NULL, NULL, __pyx_t_7);
+    if (unlikely(__pyx_t_8 == 0)) break;
+    if (unlikely(__pyx_t_8 == -1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_XDECREF_SET(__pyx_v_sig, __pyx_t_1);
+    __pyx_t_1 = 0;
+    __pyx_v_match_found = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_sig, __pyx_n_s_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_split); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_9);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_9);
+    __Pyx_INCREF(__pyx_v_dest_sig);
+    __Pyx_GIVEREF(__pyx_v_dest_sig);
+    PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_dest_sig);
+    __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_builtin_zip, __pyx_t_1, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    if (likely(PyList_CheckExact(__pyx_t_9)) || PyTuple_CheckExact(__pyx_t_9)) {
+      __pyx_t_1 = __pyx_t_9; __Pyx_INCREF(__pyx_t_1); __pyx_t_10 = 0;
+      __pyx_t_11 = NULL;
+    } else {
+      __pyx_t_10 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_1);
+      __pyx_t_11 = Py_TYPE(__pyx_t_1)->tp_iternext; if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    for (;;) {
+      if (likely(!__pyx_t_11)) {
+        if (likely(PyList_CheckExact(__pyx_t_1))) {
+          if (__pyx_t_10 >= PyList_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        } else {
+          if (__pyx_t_10 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        }
+      } else {
+        __pyx_t_9 = __pyx_t_11(__pyx_t_1);
+        if (unlikely(!__pyx_t_9)) {
+          PyObject* exc_type = PyErr_Occurred();
+          if (exc_type) {
+            if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
+            else __PYX_ERR(0, 20, __pyx_L1_error)
+          }
+          break;
+        }
+        __Pyx_GOTREF(__pyx_t_9);
+      }
+      if ((likely(PyTuple_CheckExact(__pyx_t_9))) || (PyList_CheckExact(__pyx_t_9))) {
+        PyObject* sequence = __pyx_t_9;
+        #if CYTHON_COMPILING_IN_CPYTHON
+        Py_ssize_t size = Py_SIZE(sequence);
+        #else
+        Py_ssize_t size = PySequence_Size(sequence);
+        #endif
+        if (unlikely(size != 2)) {
+          if (size > 2) __Pyx_RaiseTooManyValuesError(2);
+          else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
+          __PYX_ERR(0, 20, __pyx_L1_error)
+        }
+        #if CYTHON_COMPILING_IN_CPYTHON
+        if (likely(PyTuple_CheckExact(sequence))) {
+          __pyx_t_12 = PyTuple_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyTuple_GET_ITEM(sequence, 1); 
+        } else {
+          __pyx_t_12 = PyList_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyList_GET_ITEM(sequence, 1); 
+        }
+        __Pyx_INCREF(__pyx_t_12);
+        __Pyx_INCREF(__pyx_t_13);
+        #else
+        __pyx_t_12 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 20, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_12);
+        __pyx_t_13 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 20, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_13);
+        #endif
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+      } else {
+        Py_ssize_t index = -1;
+        __pyx_t_14 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 20, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_14);
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        __pyx_t_15 = Py_TYPE(__pyx_t_14)->tp_iternext;
+        index = 0; __pyx_t_12 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_12)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_12);
+        index = 1; __pyx_t_13 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_13)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_13);
+        if (__Pyx_IternextUnpackEndCheck(__pyx_t_15(__pyx_t_14), 2) < 0) __PYX_ERR(0, 20, __pyx_L1_error)
+        __pyx_t_15 = NULL;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        goto __pyx_L14_unpacking_done;
+        __pyx_L13_unpacking_failed:;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        __pyx_t_15 = NULL;
+        if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
+        __PYX_ERR(0, 20, __pyx_L1_error)
+        __pyx_L14_unpacking_done:;
+      }
+      __Pyx_XDECREF_SET(__pyx_v_src_type, __pyx_t_12);
+      __pyx_t_12 = 0;
+      __Pyx_XDECREF_SET(__pyx_v_dst_type, __pyx_t_13);
+      __pyx_t_13 = 0;
+      __pyx_t_2 = (__pyx_v_dst_type != Py_None);
+      __pyx_t_3 = (__pyx_t_2 != 0);
+      if (__pyx_t_3) {
+        __pyx_t_9 = PyObject_RichCompare(__pyx_v_src_type, __pyx_v_dst_type, Py_EQ); __Pyx_XGOTREF(__pyx_t_9); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 20, __pyx_L1_error)
+        __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_9); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 20, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        if (__pyx_t_3) {
+          __pyx_v_match_found = 1;
+          goto __pyx_L16;
+        }
+        /*else*/ {
+          __pyx_v_match_found = 0;
+          goto __pyx_L12_break;
+        }
+        __pyx_L16:;
+      }
+    }
+    __pyx_L12_break:;
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_3 = (__pyx_v_match_found != 0);
+    if (__pyx_t_3) {
+      __pyx_t_16 = __Pyx_PyList_Append(__pyx_v_candidates, __pyx_v_sig); if (unlikely(__pyx_t_16 == -1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+  }
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+  __pyx_t_3 = (__pyx_v_candidates != Py_None) && (PyList_GET_SIZE(__pyx_v_candidates) != 0);
+  __pyx_t_2 = ((!__pyx_t_3) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__5, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  __pyx_t_6 = PyList_GET_SIZE(__pyx_v_candidates); if (unlikely(__pyx_t_6 == -1)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_t_2 = ((__pyx_t_6 > 1) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 20, __pyx_L1_error)
+  }
+  /*else*/ {
+    __Pyx_XDECREF(__pyx_r);
+    if (unlikely(__pyx_v_signatures == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 20, __pyx_L1_error)
+    }
+    __pyx_t_5 = __Pyx_GetItemInt_List(__pyx_v_candidates, 0, long, 1, __Pyx_PyInt_From_long, 1, 0, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_signatures), __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __pyx_r = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L0;
+  }
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_9);
+  __Pyx_XDECREF(__pyx_t_12);
+  __Pyx_XDECREF(__pyx_t_13);
+  __Pyx_XDECREF(__pyx_t_14);
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XDECREF(__pyx_v_dest_sig);
+  __Pyx_XDECREF(__pyx_v_arg);
+  __Pyx_XDECREF(__pyx_v_candidates);
+  __Pyx_XDECREF(__pyx_v_sig);
+  __Pyx_XDECREF(__pyx_v_src_type);
+  __Pyx_XDECREF(__pyx_v_dst_type);
+  __Pyx_XDECREF(__pyx_v_kwargs);
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_7head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_7head = {"__pyx_fuse_0head", (PyCFunction)__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_7head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_head};
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_7head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf = 0;
+  Py_ssize_t __pyx_v_lines;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("head (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf,&__pyx_n_s_lines,0};
+    PyObject* values[2] = {0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_lines)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("head", 1, 2, 2, 1); __PYX_ERR(0, 20, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "head") < 0)) __PYX_ERR(0, 20, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 2) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+    }
+    __pyx_v_buf = ((PyObject*)values[0]);
+    __pyx_v_lines = __Pyx_PyIndex_AsSsize_t(values[1]); if (unlikely((__pyx_v_lines == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 20, __pyx_L3_error)
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("head", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 20, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf), (&PyBytes_Type), 1, "buf", 1))) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_6head(__pyx_self, __pyx_v_buf, __pyx_v_lines);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_6head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_lines) {
+  Py_ssize_t __pyx_v_pos;
+  Py_ssize_t __pyx_v_linebreaks_seen;
+  Py_ssize_t __pyx_v_length;
+  unsigned char *__pyx_v_data;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  Py_ssize_t __pyx_t_1;
+  unsigned char *__pyx_t_2;
+  int __pyx_t_3;
+  int __pyx_t_4;
+  PyObject *__pyx_t_5 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_0head", 0);
+
+  __pyx_v_pos = 0;
+
+  __pyx_v_linebreaks_seen = 0;
+
+  if (unlikely(__pyx_v_buf == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+    __PYX_ERR(0, 28, __pyx_L1_error)
+  }
+  __pyx_t_1 = PyBytes_GET_SIZE(__pyx_v_buf); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 28, __pyx_L1_error)
+  __pyx_v_length = __pyx_t_1;
+
+  __pyx_t_2 = __Pyx_PyObject_AsUString(__pyx_v_buf); if (unlikely((!__pyx_t_2) && PyErr_Occurred())) __PYX_ERR(0, 29, __pyx_L1_error)
+  __pyx_v_data = __pyx_t_2;
+
+  while (1) {
+    __pyx_t_4 = ((__pyx_v_linebreaks_seen < __pyx_v_lines) != 0);
+    if (__pyx_t_4) {
+    } else {
+      __pyx_t_3 = __pyx_t_4;
+      goto __pyx_L5_bool_binop_done;
+    }
+    __pyx_t_4 = ((__pyx_v_pos < __pyx_v_length) != 0);
+    __pyx_t_3 = __pyx_t_4;
+    __pyx_L5_bool_binop_done:;
+    if (!__pyx_t_3) break;
+
+    __pyx_t_3 = (((__pyx_v_data[__pyx_v_pos]) == '\n') != 0);
+    if (__pyx_t_3) {
+
+      __pyx_v_linebreaks_seen = (__pyx_v_linebreaks_seen + 1);
+
+    }
+
+    __pyx_v_pos = (__pyx_v_pos + 1);
+  }
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_5 = PyInt_FromSsize_t(__pyx_v_pos); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 35, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_r = __pyx_t_5;
+  __pyx_t_5 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_AddTraceback("cutadapt._seqio.head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_9head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_9head = {"__pyx_fuse_1head", (PyCFunction)__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_9head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_head};
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_9head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf = 0;
+  Py_ssize_t __pyx_v_lines;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("head (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf,&__pyx_n_s_lines,0};
+    PyObject* values[2] = {0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_lines)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("head", 1, 2, 2, 1); __PYX_ERR(0, 20, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "head") < 0)) __PYX_ERR(0, 20, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 2) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+    }
+    __pyx_v_buf = ((PyObject*)values[0]);
+    __pyx_v_lines = __Pyx_PyIndex_AsSsize_t(values[1]); if (unlikely((__pyx_v_lines == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 20, __pyx_L3_error)
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("head", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 20, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf), (&PyByteArray_Type), 1, "buf", 1))) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_8head(__pyx_self, __pyx_v_buf, __pyx_v_lines);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_8head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_lines) {
+  Py_ssize_t __pyx_v_pos;
+  Py_ssize_t __pyx_v_linebreaks_seen;
+  Py_ssize_t __pyx_v_length;
+  unsigned char *__pyx_v_data;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  Py_ssize_t __pyx_t_1;
+  unsigned char *__pyx_t_2;
+  int __pyx_t_3;
+  int __pyx_t_4;
+  PyObject *__pyx_t_5 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_1head", 0);
+
+  __pyx_v_pos = 0;
+
+  __pyx_v_linebreaks_seen = 0;
+
+  __pyx_t_1 = PyObject_Length(__pyx_v_buf); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 28, __pyx_L1_error)
+  __pyx_v_length = __pyx_t_1;
+
+  __pyx_t_2 = __Pyx_PyObject_AsUString(__pyx_v_buf); if (unlikely((!__pyx_t_2) && PyErr_Occurred())) __PYX_ERR(0, 29, __pyx_L1_error)
+  __pyx_v_data = __pyx_t_2;
+
+  while (1) {
+    __pyx_t_4 = ((__pyx_v_linebreaks_seen < __pyx_v_lines) != 0);
+    if (__pyx_t_4) {
+    } else {
+      __pyx_t_3 = __pyx_t_4;
+      goto __pyx_L5_bool_binop_done;
+    }
+    __pyx_t_4 = ((__pyx_v_pos < __pyx_v_length) != 0);
+    __pyx_t_3 = __pyx_t_4;
+    __pyx_L5_bool_binop_done:;
+    if (!__pyx_t_3) break;
+
+    __pyx_t_3 = (((__pyx_v_data[__pyx_v_pos]) == '\n') != 0);
+    if (__pyx_t_3) {
+
+      __pyx_v_linebreaks_seen = (__pyx_v_linebreaks_seen + 1);
+
+    }
+
+    __pyx_v_pos = (__pyx_v_pos + 1);
+  }
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_5 = PyInt_FromSsize_t(__pyx_v_pos); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 35, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_r = __pyx_t_5;
+  __pyx_t_5 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_AddTraceback("cutadapt._seqio.head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+
+/* Python wrapper */
+static PyObject *__pyx_pw_8cutadapt_6_seqio_3fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_8cutadapt_6_seqio_2fastq_head[] = "\n\tReturn an integer length such that buf[:length] contains the highest\n\tpossible number of complete four-line records.\n\n\tIf end is -1, the full buffer is searched. Otherwise only buf[:end].\n\t";
+static PyMethodDef __pyx_mdef_8cutadapt_6_seqio_3fastq_head = {"fastq_head", (PyCFunction)__pyx_pw_8cutadapt_6_seqio_3fastq_head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_2fastq_head};
+static PyObject *__pyx_pw_8cutadapt_6_seqio_3fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_signatures = 0;
+  PyObject *__pyx_v_args = 0;
+  PyObject *__pyx_v_kwargs = 0;
+  CYTHON_UNUSED PyObject *__pyx_v_defaults = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("__pyx_fused_cpdef (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_signatures,&__pyx_n_s_args,&__pyx_n_s_kwargs,&__pyx_n_s_defaults,0};
+    PyObject* values[4] = {0,0,0,0};
+    values[1] = __pyx_k__7;
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_signatures)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_args);
+          if (value) { values[1] = value; kw_args--; }
+        }
+        case  2:
+        if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_kwargs)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 2); __PYX_ERR(0, 38, __pyx_L3_error)
+        }
+        case  3:
+        if (likely((values[3] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_defaults)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 3); __PYX_ERR(0, 38, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__pyx_fused_cpdef") < 0)) __PYX_ERR(0, 38, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+    }
+    __pyx_v_signatures = values[0];
+    __pyx_v_args = values[1];
+    __pyx_v_kwargs = values[2];
+    __pyx_v_defaults = values[3];
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 38, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_2fastq_head(__pyx_self, __pyx_v_signatures, __pyx_v_args, __pyx_v_kwargs, __pyx_v_defaults);
+
+  /* function exit code */
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_2fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults) {
+  PyObject *__pyx_v_dest_sig = NULL;
+  PyObject *__pyx_v_arg = NULL;
+  PyObject *__pyx_v_candidates = NULL;
+  PyObject *__pyx_v_sig = NULL;
+  int __pyx_v_match_found;
+  PyObject *__pyx_v_src_type = NULL;
+  PyObject *__pyx_v_dst_type = NULL;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  int __pyx_t_2;
+  int __pyx_t_3;
+  Py_ssize_t __pyx_t_4;
+  PyObject *__pyx_t_5 = NULL;
+  Py_ssize_t __pyx_t_6;
+  int __pyx_t_7;
+  int __pyx_t_8;
+  PyObject *__pyx_t_9 = NULL;
+  Py_ssize_t __pyx_t_10;
+  PyObject *(*__pyx_t_11)(PyObject *);
+  PyObject *__pyx_t_12 = NULL;
+  PyObject *__pyx_t_13 = NULL;
+  PyObject *__pyx_t_14 = NULL;
+  PyObject *(*__pyx_t_15)(PyObject *);
+  int __pyx_t_16;
+  __Pyx_RefNannySetupContext("fastq_head", 0);
+  __Pyx_INCREF(__pyx_v_kwargs);
+  __pyx_t_1 = PyList_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_INCREF(Py_None);
+  __Pyx_GIVEREF(Py_None);
+  PyList_SET_ITEM(__pyx_t_1, 0, Py_None);
+  __pyx_v_dest_sig = ((PyObject*)__pyx_t_1);
+  __pyx_t_1 = 0;
+  __pyx_t_2 = (__pyx_v_kwargs == Py_None);
+  __pyx_t_3 = (__pyx_t_2 != 0);
+  if (__pyx_t_3) {
+    __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF_SET(__pyx_v_kwargs, __pyx_t_1);
+    __pyx_t_1 = 0;
+  }
+  if (unlikely(__pyx_v_args == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __pyx_t_3 = ((0 < __pyx_t_4) != 0);
+  if (__pyx_t_3) {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_GetItemInt_Tuple(((PyObject*)__pyx_v_args), 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  if (unlikely(__pyx_v_kwargs == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  __pyx_t_3 = (__Pyx_PyDict_ContainsTF(__pyx_n_s_buf, ((PyObject*)__pyx_v_kwargs), Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __pyx_t_2 = (__pyx_t_3 != 0);
+  if (__pyx_t_2) {
+    if (unlikely(__pyx_v_kwargs == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_kwargs), __pyx_n_s_buf); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  /*else*/ {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+      __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+    __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __pyx_t_1 = PyInt_FromSsize_t(__pyx_t_4); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_5 = __Pyx_PyString_Format(__pyx_kp_s_Expected_at_least_d_arguments, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_5);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_5);
+    __pyx_t_5 = 0;
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  __pyx_L4:;
+  while (1) {
+    __pyx_t_2 = PyBytes_Check(__pyx_v_arg); 
+    __pyx_t_3 = (__pyx_t_2 != 0);
+    if (__pyx_t_3) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytes, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    __pyx_t_3 = PyByteArray_Check(__pyx_v_arg); 
+    __pyx_t_2 = (__pyx_t_3 != 0);
+    if (__pyx_t_2) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytearray, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, Py_None, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+    goto __pyx_L6_break;
+  }
+  __pyx_L6_break:;
+  __pyx_t_5 = PyList_New(0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_v_candidates = ((PyObject*)__pyx_t_5);
+  __pyx_t_5 = 0;
+  __pyx_t_4 = 0;
+  if (unlikely(__pyx_v_signatures == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  __pyx_t_1 = __Pyx_dict_iterator(((PyObject*)__pyx_v_signatures), 1, ((PyObject *)NULL), (&__pyx_t_6), (&__pyx_t_7)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __pyx_t_5 = __pyx_t_1;
+  __pyx_t_1 = 0;
+  while (1) {
+    __pyx_t_8 = __Pyx_dict_iter_next(__pyx_t_5, __pyx_t_6, &__pyx_t_4, &__pyx_t_1, NULL, NULL, __pyx_t_7);
+    if (unlikely(__pyx_t_8 == 0)) break;
+    if (unlikely(__pyx_t_8 == -1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_XDECREF_SET(__pyx_v_sig, __pyx_t_1);
+    __pyx_t_1 = 0;
+    __pyx_v_match_found = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_sig, __pyx_n_s_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__8, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_split); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__9, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_9);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_9);
+    __Pyx_INCREF(__pyx_v_dest_sig);
+    __Pyx_GIVEREF(__pyx_v_dest_sig);
+    PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_dest_sig);
+    __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_builtin_zip, __pyx_t_1, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    if (likely(PyList_CheckExact(__pyx_t_9)) || PyTuple_CheckExact(__pyx_t_9)) {
+      __pyx_t_1 = __pyx_t_9; __Pyx_INCREF(__pyx_t_1); __pyx_t_10 = 0;
+      __pyx_t_11 = NULL;
+    } else {
+      __pyx_t_10 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_1);
+      __pyx_t_11 = Py_TYPE(__pyx_t_1)->tp_iternext; if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    for (;;) {
+      if (likely(!__pyx_t_11)) {
+        if (likely(PyList_CheckExact(__pyx_t_1))) {
+          if (__pyx_t_10 >= PyList_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        } else {
+          if (__pyx_t_10 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        }
+      } else {
+        __pyx_t_9 = __pyx_t_11(__pyx_t_1);
+        if (unlikely(!__pyx_t_9)) {
+          PyObject* exc_type = PyErr_Occurred();
+          if (exc_type) {
+            if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
+            else __PYX_ERR(0, 38, __pyx_L1_error)
+          }
+          break;
+        }
+        __Pyx_GOTREF(__pyx_t_9);
+      }
+      if ((likely(PyTuple_CheckExact(__pyx_t_9))) || (PyList_CheckExact(__pyx_t_9))) {
+        PyObject* sequence = __pyx_t_9;
+        #if CYTHON_COMPILING_IN_CPYTHON
+        Py_ssize_t size = Py_SIZE(sequence);
+        #else
+        Py_ssize_t size = PySequence_Size(sequence);
+        #endif
+        if (unlikely(size != 2)) {
+          if (size > 2) __Pyx_RaiseTooManyValuesError(2);
+          else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
+          __PYX_ERR(0, 38, __pyx_L1_error)
+        }
+        #if CYTHON_COMPILING_IN_CPYTHON
+        if (likely(PyTuple_CheckExact(sequence))) {
+          __pyx_t_12 = PyTuple_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyTuple_GET_ITEM(sequence, 1); 
+        } else {
+          __pyx_t_12 = PyList_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyList_GET_ITEM(sequence, 1); 
+        }
+        __Pyx_INCREF(__pyx_t_12);
+        __Pyx_INCREF(__pyx_t_13);
+        #else
+        __pyx_t_12 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 38, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_12);
+        __pyx_t_13 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 38, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_13);
+        #endif
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+      } else {
+        Py_ssize_t index = -1;
+        __pyx_t_14 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 38, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_14);
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        __pyx_t_15 = Py_TYPE(__pyx_t_14)->tp_iternext;
+        index = 0; __pyx_t_12 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_12)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_12);
+        index = 1; __pyx_t_13 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_13)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_13);
+        if (__Pyx_IternextUnpackEndCheck(__pyx_t_15(__pyx_t_14), 2) < 0) __PYX_ERR(0, 38, __pyx_L1_error)
+        __pyx_t_15 = NULL;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        goto __pyx_L14_unpacking_done;
+        __pyx_L13_unpacking_failed:;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        __pyx_t_15 = NULL;
+        if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
+        __PYX_ERR(0, 38, __pyx_L1_error)
+        __pyx_L14_unpacking_done:;
+      }
+      __Pyx_XDECREF_SET(__pyx_v_src_type, __pyx_t_12);
+      __pyx_t_12 = 0;
+      __Pyx_XDECREF_SET(__pyx_v_dst_type, __pyx_t_13);
+      __pyx_t_13 = 0;
+      __pyx_t_2 = (__pyx_v_dst_type != Py_None);
+      __pyx_t_3 = (__pyx_t_2 != 0);
+      if (__pyx_t_3) {
+        __pyx_t_9 = PyObject_RichCompare(__pyx_v_src_type, __pyx_v_dst_type, Py_EQ); __Pyx_XGOTREF(__pyx_t_9); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 38, __pyx_L1_error)
+        __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_9); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        if (__pyx_t_3) {
+          __pyx_v_match_found = 1;
+          goto __pyx_L16;
+        }
+        /*else*/ {
+          __pyx_v_match_found = 0;
+          goto __pyx_L12_break;
+        }
+        __pyx_L16:;
+      }
+    }
+    __pyx_L12_break:;
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_3 = (__pyx_v_match_found != 0);
+    if (__pyx_t_3) {
+      __pyx_t_16 = __Pyx_PyList_Append(__pyx_v_candidates, __pyx_v_sig); if (unlikely(__pyx_t_16 == -1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+  }
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+  __pyx_t_3 = (__pyx_v_candidates != Py_None) && (PyList_GET_SIZE(__pyx_v_candidates) != 0);
+  __pyx_t_2 = ((!__pyx_t_3) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__10, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  __pyx_t_6 = PyList_GET_SIZE(__pyx_v_candidates); if (unlikely(__pyx_t_6 == -1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __pyx_t_2 = ((__pyx_t_6 > 1) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__11, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 38, __pyx_L1_error)
+  }
+  /*else*/ {
+    __Pyx_XDECREF(__pyx_r);
+    if (unlikely(__pyx_v_signatures == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 38, __pyx_L1_error)
+    }
+    __pyx_t_5 = __Pyx_GetItemInt_List(__pyx_v_candidates, 0, long, 1, __Pyx_PyInt_From_long, 1, 0, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_signatures), __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __pyx_r = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L0;
+  }
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_9);
+  __Pyx_XDECREF(__pyx_t_12);
+  __Pyx_XDECREF(__pyx_t_13);
+  __Pyx_XDECREF(__pyx_t_14);
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XDECREF(__pyx_v_dest_sig);
+  __Pyx_XDECREF(__pyx_v_arg);
+  __Pyx_XDECREF(__pyx_v_candidates);
+  __Pyx_XDECREF(__pyx_v_sig);
+  __Pyx_XDECREF(__pyx_v_src_type);
+  __Pyx_XDECREF(__pyx_v_dst_type);
+  __Pyx_XDECREF(__pyx_v_kwargs);
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_28__defaults__(CYTHON_UNUSED PyObject *__pyx_self) {
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  PyObject *__pyx_t_2 = NULL;
+  __Pyx_RefNannySetupContext("__defaults__", 0);
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_1 = PyInt_FromSsize_t(__Pyx_CyFunction_Defaults(__pyx_defaults2, __pyx_self)->__pyx_arg_end); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_t_2 = PyTuple_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_2);
+  __Pyx_GIVEREF(__pyx_t_1);
+  PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_1);
+  __pyx_t_1 = 0;
+  __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_GIVEREF(__pyx_t_2);
+  PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_2);
+  __Pyx_INCREF(Py_None);
+  __Pyx_GIVEREF(Py_None);
+  PyTuple_SET_ITEM(__pyx_t_1, 1, Py_None);
+  __pyx_t_2 = 0;
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_2);
+  __Pyx_AddTraceback("cutadapt._seqio.__defaults__", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_13fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_13fastq_head = {"__pyx_fuse_0fastq_head", (PyCFunction)__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_13fastq_head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_2fastq_head};
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_13fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf = 0;
+  Py_ssize_t __pyx_v_end;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("fastq_head (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf,&__pyx_n_s_end,0};
+    PyObject* values[2] = {0,0};
+    __pyx_defaults2 *__pyx_dynamic_args = __Pyx_CyFunction_Defaults(__pyx_defaults2, __pyx_self);
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end);
+          if (value) { values[1] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "fastq_head") < 0)) __PYX_ERR(0, 38, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_buf = ((PyObject*)values[0]);
+    if (values[1]) {
+      __pyx_v_end = __Pyx_PyIndex_AsSsize_t(values[1]); if (unlikely((__pyx_v_end == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 38, __pyx_L3_error)
+    } else {
+      __pyx_v_end = __pyx_dynamic_args->__pyx_arg_end;
+    }
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("fastq_head", 0, 1, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 38, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.fastq_head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf), (&PyBytes_Type), 1, "buf", 1))) __PYX_ERR(0, 38, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_12fastq_head(__pyx_self, __pyx_v_buf, __pyx_v_end);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_12fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_end) {
+  Py_ssize_t __pyx_v_pos;
+  Py_ssize_t __pyx_v_linebreaks;
+  Py_ssize_t __pyx_v_length;
+  unsigned char *__pyx_v_data;
+  Py_ssize_t __pyx_v_record_start;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  Py_ssize_t __pyx_t_1;
+  unsigned char *__pyx_t_2;
+  int __pyx_t_3;
+  Py_ssize_t __pyx_t_4;
+  Py_ssize_t __pyx_t_5;
+  int __pyx_t_6;
+  PyObject *__pyx_t_7 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_0fastq_head", 0);
+
+  __pyx_v_pos = 0;
+
+  __pyx_v_linebreaks = 0;
+
+  if (unlikely(__pyx_v_buf == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+    __PYX_ERR(0, 48, __pyx_L1_error)
+  }
+  __pyx_t_1 = PyBytes_GET_SIZE(__pyx_v_buf); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 48, __pyx_L1_error)
+  __pyx_v_length = __pyx_t_1;
+
+  __pyx_t_2 = __Pyx_PyObject_AsUString(__pyx_v_buf); if (unlikely((!__pyx_t_2) && PyErr_Occurred())) __PYX_ERR(0, 49, __pyx_L1_error)
+  __pyx_v_data = __pyx_t_2;
+
+  __pyx_v_record_start = 0;
+
+  __pyx_t_3 = ((__pyx_v_end != -1L) != 0);
+  if (__pyx_t_3) {
+
+    __pyx_t_1 = __pyx_v_end;
+    __pyx_t_4 = __pyx_v_length;
+    if (((__pyx_t_1 < __pyx_t_4) != 0)) {
+      __pyx_t_5 = __pyx_t_1;
+    } else {
+      __pyx_t_5 = __pyx_t_4;
+    }
+    __pyx_v_length = __pyx_t_5;
+
+  }
+
+  while (1) {
+
+    while (1) {
+      __pyx_t_6 = ((__pyx_v_pos < __pyx_v_length) != 0);
+      if (__pyx_t_6) {
+      } else {
+        __pyx_t_3 = __pyx_t_6;
+        goto __pyx_L8_bool_binop_done;
+      }
+      __pyx_t_6 = (((__pyx_v_data[__pyx_v_pos]) != '\n') != 0);
+      __pyx_t_3 = __pyx_t_6;
+      __pyx_L8_bool_binop_done:;
+      if (!__pyx_t_3) break;
+
+      __pyx_v_pos = (__pyx_v_pos + 1);
+    }
+
+    __pyx_t_3 = ((__pyx_v_pos == __pyx_v_length) != 0);
+    if (__pyx_t_3) {
+
+      goto __pyx_L5_break;
+
+    }
+
+    __pyx_v_pos = (__pyx_v_pos + 1);
+
+    __pyx_v_linebreaks = (__pyx_v_linebreaks + 1);
+
+    __pyx_t_3 = ((__pyx_v_linebreaks == 4) != 0);
+    if (__pyx_t_3) {
+
+      __pyx_v_linebreaks = 0;
+
+      __pyx_v_record_start = __pyx_v_pos;
+
+    }
+  }
+  __pyx_L5_break:;
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_7 = PyInt_FromSsize_t(__pyx_v_record_start); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 66, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_7);
+  __pyx_r = __pyx_t_7;
+  __pyx_t_7 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_7);
+  __Pyx_AddTraceback("cutadapt._seqio.fastq_head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_30__defaults__(CYTHON_UNUSED PyObject *__pyx_self) {
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  PyObject *__pyx_t_2 = NULL;
+  __Pyx_RefNannySetupContext("__defaults__", 0);
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_1 = PyInt_FromSsize_t(__Pyx_CyFunction_Defaults(__pyx_defaults3, __pyx_self)->__pyx_arg_end); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __pyx_t_2 = PyTuple_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_2);
+  __Pyx_GIVEREF(__pyx_t_1);
+  PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_1);
+  __pyx_t_1 = 0;
+  __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_GIVEREF(__pyx_t_2);
+  PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_2);
+  __Pyx_INCREF(Py_None);
+  __Pyx_GIVEREF(Py_None);
+  PyTuple_SET_ITEM(__pyx_t_1, 1, Py_None);
+  __pyx_t_2 = 0;
+  __pyx_r = __pyx_t_1;
+  __pyx_t_1 = 0;
+  goto __pyx_L0;
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_2);
+  __Pyx_AddTraceback("cutadapt._seqio.__defaults__", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_15fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_15fastq_head = {"__pyx_fuse_1fastq_head", (PyCFunction)__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_15fastq_head, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_2fastq_head};
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_15fastq_head(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf = 0;
+  Py_ssize_t __pyx_v_end;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("fastq_head (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf,&__pyx_n_s_end,0};
+    PyObject* values[2] = {0,0};
+    __pyx_defaults3 *__pyx_dynamic_args = __Pyx_CyFunction_Defaults(__pyx_defaults3, __pyx_self);
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end);
+          if (value) { values[1] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "fastq_head") < 0)) __PYX_ERR(0, 38, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_buf = ((PyObject*)values[0]);
+    if (values[1]) {
+      __pyx_v_end = __Pyx_PyIndex_AsSsize_t(values[1]); if (unlikely((__pyx_v_end == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 38, __pyx_L3_error)
+    } else {
+      __pyx_v_end = __pyx_dynamic_args->__pyx_arg_end;
+    }
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("fastq_head", 0, 1, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 38, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.fastq_head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf), (&PyByteArray_Type), 1, "buf", 1))) __PYX_ERR(0, 38, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_14fastq_head(__pyx_self, __pyx_v_buf, __pyx_v_end);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_14fastq_head(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf, Py_ssize_t __pyx_v_end) {
+  Py_ssize_t __pyx_v_pos;
+  Py_ssize_t __pyx_v_linebreaks;
+  Py_ssize_t __pyx_v_length;
+  unsigned char *__pyx_v_data;
+  Py_ssize_t __pyx_v_record_start;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  Py_ssize_t __pyx_t_1;
+  unsigned char *__pyx_t_2;
+  int __pyx_t_3;
+  Py_ssize_t __pyx_t_4;
+  Py_ssize_t __pyx_t_5;
+  int __pyx_t_6;
+  PyObject *__pyx_t_7 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_1fastq_head", 0);
+
+  __pyx_v_pos = 0;
+
+  __pyx_v_linebreaks = 0;
+
+  __pyx_t_1 = PyObject_Length(__pyx_v_buf); if (unlikely(__pyx_t_1 == -1)) __PYX_ERR(0, 48, __pyx_L1_error)
+  __pyx_v_length = __pyx_t_1;
+
+  __pyx_t_2 = __Pyx_PyObject_AsUString(__pyx_v_buf); if (unlikely((!__pyx_t_2) && PyErr_Occurred())) __PYX_ERR(0, 49, __pyx_L1_error)
+  __pyx_v_data = __pyx_t_2;
+
+  __pyx_v_record_start = 0;
+
+  __pyx_t_3 = ((__pyx_v_end != -1L) != 0);
+  if (__pyx_t_3) {
+
+    __pyx_t_1 = __pyx_v_end;
+    __pyx_t_4 = __pyx_v_length;
+    if (((__pyx_t_1 < __pyx_t_4) != 0)) {
+      __pyx_t_5 = __pyx_t_1;
+    } else {
+      __pyx_t_5 = __pyx_t_4;
+    }
+    __pyx_v_length = __pyx_t_5;
+
+  }
+
+  while (1) {
+
+    while (1) {
+      __pyx_t_6 = ((__pyx_v_pos < __pyx_v_length) != 0);
+      if (__pyx_t_6) {
+      } else {
+        __pyx_t_3 = __pyx_t_6;
+        goto __pyx_L8_bool_binop_done;
+      }
+      __pyx_t_6 = (((__pyx_v_data[__pyx_v_pos]) != '\n') != 0);
+      __pyx_t_3 = __pyx_t_6;
+      __pyx_L8_bool_binop_done:;
+      if (!__pyx_t_3) break;
+
+      __pyx_v_pos = (__pyx_v_pos + 1);
+    }
+
+    __pyx_t_3 = ((__pyx_v_pos == __pyx_v_length) != 0);
+    if (__pyx_t_3) {
+
+      goto __pyx_L5_break;
+
+    }
+
+    __pyx_v_pos = (__pyx_v_pos + 1);
+
+    __pyx_v_linebreaks = (__pyx_v_linebreaks + 1);
+
+    __pyx_t_3 = ((__pyx_v_linebreaks == 4) != 0);
+    if (__pyx_t_3) {
+
+      __pyx_v_linebreaks = 0;
+
+      __pyx_v_record_start = __pyx_v_pos;
+
+    }
+  }
+  __pyx_L5_break:;
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_7 = PyInt_FromSsize_t(__pyx_v_record_start); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 66, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_7);
+  __pyx_r = __pyx_t_7;
+  __pyx_t_7 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_7);
+  __Pyx_AddTraceback("cutadapt._seqio.fastq_head", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+
+/* Python wrapper */
+static PyObject *__pyx_pw_8cutadapt_6_seqio_5two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_8cutadapt_6_seqio_4two_fastq_heads[] = "\n\tSkip forward in the two buffers by multiples of four lines.\n\n\tReturn a tuple (length1, length2) such that buf1[:length1] and\n\tbuf2[:length2] contain the same number of lines (where the\n\tline number is divisible by four).\n\t";
+static PyMethodDef __pyx_mdef_8cutadapt_6_seqio_5two_fastq_heads = {"two_fastq_heads", (PyCFunction)__pyx_pw_8cutadapt_6_seqio_5two_fastq_heads, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_4two_fastq_heads};
+static PyObject *__pyx_pw_8cutadapt_6_seqio_5two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_signatures = 0;
+  PyObject *__pyx_v_args = 0;
+  PyObject *__pyx_v_kwargs = 0;
+  CYTHON_UNUSED PyObject *__pyx_v_defaults = 0;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("__pyx_fused_cpdef (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_signatures,&__pyx_n_s_args,&__pyx_n_s_kwargs,&__pyx_n_s_defaults,0};
+    PyObject* values[4] = {0,0,0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_signatures)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_args)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 1); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  2:
+        if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_kwargs)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 2); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  3:
+        if (likely((values[3] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_defaults)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, 3); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__pyx_fused_cpdef") < 0)) __PYX_ERR(0, 69, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+    }
+    __pyx_v_signatures = values[0];
+    __pyx_v_args = values[1];
+    __pyx_v_kwargs = values[2];
+    __pyx_v_defaults = values[3];
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("__pyx_fused_cpdef", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 69, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_4two_fastq_heads(__pyx_self, __pyx_v_signatures, __pyx_v_args, __pyx_v_kwargs, __pyx_v_defaults);
+
+  /* function exit code */
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_4two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_signatures, PyObject *__pyx_v_args, PyObject *__pyx_v_kwargs, CYTHON_UNUSED PyObject *__pyx_v_defaults) {
+  PyObject *__pyx_v_dest_sig = NULL;
+  PyObject *__pyx_v_arg = NULL;
+  PyObject *__pyx_v_candidates = NULL;
+  PyObject *__pyx_v_sig = NULL;
+  int __pyx_v_match_found;
+  PyObject *__pyx_v_src_type = NULL;
+  PyObject *__pyx_v_dst_type = NULL;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  PyObject *__pyx_t_1 = NULL;
+  int __pyx_t_2;
+  int __pyx_t_3;
+  Py_ssize_t __pyx_t_4;
+  PyObject *__pyx_t_5 = NULL;
+  Py_ssize_t __pyx_t_6;
+  int __pyx_t_7;
+  int __pyx_t_8;
+  PyObject *__pyx_t_9 = NULL;
+  Py_ssize_t __pyx_t_10;
+  PyObject *(*__pyx_t_11)(PyObject *);
+  PyObject *__pyx_t_12 = NULL;
+  PyObject *__pyx_t_13 = NULL;
+  PyObject *__pyx_t_14 = NULL;
+  PyObject *(*__pyx_t_15)(PyObject *);
+  int __pyx_t_16;
+  __Pyx_RefNannySetupContext("two_fastq_heads", 0);
+  __Pyx_INCREF(__pyx_v_kwargs);
+  __pyx_t_1 = PyList_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_INCREF(Py_None);
+  __Pyx_GIVEREF(Py_None);
+  PyList_SET_ITEM(__pyx_t_1, 0, Py_None);
+  __pyx_v_dest_sig = ((PyObject*)__pyx_t_1);
+  __pyx_t_1 = 0;
+  __pyx_t_2 = (__pyx_v_kwargs == Py_None);
+  __pyx_t_3 = (__pyx_t_2 != 0);
+  if (__pyx_t_3) {
+    __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF_SET(__pyx_v_kwargs, __pyx_t_1);
+    __pyx_t_1 = 0;
+  }
+  if (unlikely(__pyx_v_args == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_3 = ((0 < __pyx_t_4) != 0);
+  if (__pyx_t_3) {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_GetItemInt_Tuple(((PyObject*)__pyx_v_args), 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  if (unlikely(__pyx_v_kwargs == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  __pyx_t_3 = (__Pyx_PyDict_ContainsTF(__pyx_n_s_buf1, ((PyObject*)__pyx_v_kwargs), Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_2 = (__pyx_t_3 != 0);
+  if (__pyx_t_2) {
+    if (unlikely(__pyx_v_kwargs == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_kwargs), __pyx_n_s_buf1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_v_arg = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L4;
+  }
+  /*else*/ {
+    if (unlikely(__pyx_v_args == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "object of type 'NoneType' has no len()");
+      __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+    __pyx_t_4 = PyTuple_GET_SIZE(((PyObject*)__pyx_v_args)); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __pyx_t_1 = PyInt_FromSsize_t(__pyx_t_4); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_5 = __Pyx_PyString_Format(__pyx_kp_s_Expected_at_least_d_arguments, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_5);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_5);
+    __pyx_t_5 = 0;
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_t_1, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  __pyx_L4:;
+  while (1) {
+    __pyx_t_2 = PyBytes_Check(__pyx_v_arg); 
+    __pyx_t_3 = (__pyx_t_2 != 0);
+    if (__pyx_t_3) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytes, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    __pyx_t_3 = PyByteArray_Check(__pyx_v_arg); 
+    __pyx_t_2 = (__pyx_t_3 != 0);
+    if (__pyx_t_2) {
+      if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, __pyx_n_s_bytearray, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+      goto __pyx_L6_break;
+    }
+    if (unlikely(__Pyx_SetItemInt(__pyx_v_dest_sig, 0, Py_None, long, 1, __Pyx_PyInt_From_long, 1, 0, 1) < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+    goto __pyx_L6_break;
+  }
+  __pyx_L6_break:;
+  __pyx_t_5 = PyList_New(0); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_v_candidates = ((PyObject*)__pyx_t_5);
+  __pyx_t_5 = 0;
+  __pyx_t_4 = 0;
+  if (unlikely(__pyx_v_signatures == Py_None)) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  __pyx_t_1 = __Pyx_dict_iterator(((PyObject*)__pyx_v_signatures), 1, ((PyObject *)NULL), (&__pyx_t_6), (&__pyx_t_7)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __pyx_t_5 = __pyx_t_1;
+  __pyx_t_1 = 0;
+  while (1) {
+    __pyx_t_8 = __Pyx_dict_iter_next(__pyx_t_5, __pyx_t_6, &__pyx_t_4, &__pyx_t_1, NULL, NULL, __pyx_t_7);
+    if (unlikely(__pyx_t_8 == 0)) break;
+    if (unlikely(__pyx_t_8 == -1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_XDECREF_SET(__pyx_v_sig, __pyx_t_1);
+    __pyx_t_1 = 0;
+    __pyx_v_match_found = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_sig, __pyx_n_s_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__12, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_9, __pyx_n_s_split); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__13, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_GIVEREF(__pyx_t_9);
+    PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_9);
+    __Pyx_INCREF(__pyx_v_dest_sig);
+    __Pyx_GIVEREF(__pyx_v_dest_sig);
+    PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_dest_sig);
+    __pyx_t_9 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_builtin_zip, __pyx_t_1, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    if (likely(PyList_CheckExact(__pyx_t_9)) || PyTuple_CheckExact(__pyx_t_9)) {
+      __pyx_t_1 = __pyx_t_9; __Pyx_INCREF(__pyx_t_1); __pyx_t_10 = 0;
+      __pyx_t_11 = NULL;
+    } else {
+      __pyx_t_10 = -1; __pyx_t_1 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_1);
+      __pyx_t_11 = Py_TYPE(__pyx_t_1)->tp_iternext; if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+    for (;;) {
+      if (likely(!__pyx_t_11)) {
+        if (likely(PyList_CheckExact(__pyx_t_1))) {
+          if (__pyx_t_10 >= PyList_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyList_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        } else {
+          if (__pyx_t_10 >= PyTuple_GET_SIZE(__pyx_t_1)) break;
+          #if CYTHON_COMPILING_IN_CPYTHON
+          __pyx_t_9 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_10); __Pyx_INCREF(__pyx_t_9); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+          #else
+          __pyx_t_9 = PySequence_ITEM(__pyx_t_1, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          #endif
+        }
+      } else {
+        __pyx_t_9 = __pyx_t_11(__pyx_t_1);
+        if (unlikely(!__pyx_t_9)) {
+          PyObject* exc_type = PyErr_Occurred();
+          if (exc_type) {
+            if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
+            else __PYX_ERR(0, 69, __pyx_L1_error)
+          }
+          break;
+        }
+        __Pyx_GOTREF(__pyx_t_9);
+      }
+      if ((likely(PyTuple_CheckExact(__pyx_t_9))) || (PyList_CheckExact(__pyx_t_9))) {
+        PyObject* sequence = __pyx_t_9;
+        #if CYTHON_COMPILING_IN_CPYTHON
+        Py_ssize_t size = Py_SIZE(sequence);
+        #else
+        Py_ssize_t size = PySequence_Size(sequence);
+        #endif
+        if (unlikely(size != 2)) {
+          if (size > 2) __Pyx_RaiseTooManyValuesError(2);
+          else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
+          __PYX_ERR(0, 69, __pyx_L1_error)
+        }
+        #if CYTHON_COMPILING_IN_CPYTHON
+        if (likely(PyTuple_CheckExact(sequence))) {
+          __pyx_t_12 = PyTuple_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyTuple_GET_ITEM(sequence, 1); 
+        } else {
+          __pyx_t_12 = PyList_GET_ITEM(sequence, 0); 
+          __pyx_t_13 = PyList_GET_ITEM(sequence, 1); 
+        }
+        __Pyx_INCREF(__pyx_t_12);
+        __Pyx_INCREF(__pyx_t_13);
+        #else
+        __pyx_t_12 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_12)) __PYX_ERR(0, 69, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_12);
+        __pyx_t_13 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 69, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_13);
+        #endif
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+      } else {
+        Py_ssize_t index = -1;
+        __pyx_t_14 = PyObject_GetIter(__pyx_t_9); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 69, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_14);
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        __pyx_t_15 = Py_TYPE(__pyx_t_14)->tp_iternext;
+        index = 0; __pyx_t_12 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_12)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_12);
+        index = 1; __pyx_t_13 = __pyx_t_15(__pyx_t_14); if (unlikely(!__pyx_t_13)) goto __pyx_L13_unpacking_failed;
+        __Pyx_GOTREF(__pyx_t_13);
+        if (__Pyx_IternextUnpackEndCheck(__pyx_t_15(__pyx_t_14), 2) < 0) __PYX_ERR(0, 69, __pyx_L1_error)
+        __pyx_t_15 = NULL;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        goto __pyx_L14_unpacking_done;
+        __pyx_L13_unpacking_failed:;
+        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        __pyx_t_15 = NULL;
+        if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
+        __PYX_ERR(0, 69, __pyx_L1_error)
+        __pyx_L14_unpacking_done:;
+      }
+      __Pyx_XDECREF_SET(__pyx_v_src_type, __pyx_t_12);
+      __pyx_t_12 = 0;
+      __Pyx_XDECREF_SET(__pyx_v_dst_type, __pyx_t_13);
+      __pyx_t_13 = 0;
+      __pyx_t_2 = (__pyx_v_dst_type != Py_None);
+      __pyx_t_3 = (__pyx_t_2 != 0);
+      if (__pyx_t_3) {
+        __pyx_t_9 = PyObject_RichCompare(__pyx_v_src_type, __pyx_v_dst_type, Py_EQ); __Pyx_XGOTREF(__pyx_t_9); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 69, __pyx_L1_error)
+        __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_9); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 69, __pyx_L1_error)
+        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+        if (__pyx_t_3) {
+          __pyx_v_match_found = 1;
+          goto __pyx_L16;
+        }
+        /*else*/ {
+          __pyx_v_match_found = 0;
+          goto __pyx_L12_break;
+        }
+        __pyx_L16:;
+      }
+    }
+    __pyx_L12_break:;
+    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+    __pyx_t_3 = (__pyx_v_match_found != 0);
+    if (__pyx_t_3) {
+      __pyx_t_16 = __Pyx_PyList_Append(__pyx_v_candidates, __pyx_v_sig); if (unlikely(__pyx_t_16 == -1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+  }
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+  __pyx_t_3 = (__pyx_v_candidates != Py_None) && (PyList_GET_SIZE(__pyx_v_candidates) != 0);
+  __pyx_t_2 = ((!__pyx_t_3) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__14, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  __pyx_t_6 = PyList_GET_SIZE(__pyx_v_candidates); if (unlikely(__pyx_t_6 == -1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_2 = ((__pyx_t_6 > 1) != 0);
+  if (__pyx_t_2) {
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_TypeError, __pyx_tuple__15, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_Raise(__pyx_t_5, 0, 0, 0);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __PYX_ERR(0, 69, __pyx_L1_error)
+  }
+  /*else*/ {
+    __Pyx_XDECREF(__pyx_r);
+    if (unlikely(__pyx_v_signatures == Py_None)) {
+      PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
+      __PYX_ERR(0, 69, __pyx_L1_error)
+    }
+    __pyx_t_5 = __Pyx_GetItemInt_List(__pyx_v_candidates, 0, long, 1, __Pyx_PyInt_From_long, 1, 0, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __pyx_t_1 = __Pyx_PyDict_GetItem(((PyObject*)__pyx_v_signatures), __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __pyx_r = __pyx_t_1;
+    __pyx_t_1 = 0;
+    goto __pyx_L0;
+  }
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_1);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_9);
+  __Pyx_XDECREF(__pyx_t_12);
+  __Pyx_XDECREF(__pyx_t_13);
+  __Pyx_XDECREF(__pyx_t_14);
+  __Pyx_AddTraceback("cutadapt._seqio.__pyx_fused_cpdef", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XDECREF(__pyx_v_dest_sig);
+  __Pyx_XDECREF(__pyx_v_arg);
+  __Pyx_XDECREF(__pyx_v_candidates);
+  __Pyx_XDECREF(__pyx_v_sig);
+  __Pyx_XDECREF(__pyx_v_src_type);
+  __Pyx_XDECREF(__pyx_v_dst_type);
+  __Pyx_XDECREF(__pyx_v_kwargs);
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_19two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_19two_fastq_heads = {"__pyx_fuse_0two_fastq_heads", (PyCFunction)__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_19two_fastq_heads, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_4two_fastq_heads};
+static PyObject *__pyx_fuse_0__pyx_pw_8cutadapt_6_seqio_19two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf1 = 0;
+  PyObject *__pyx_v_buf2 = 0;
+  Py_ssize_t __pyx_v_end1;
+  Py_ssize_t __pyx_v_end2;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("two_fastq_heads (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf1,&__pyx_n_s_buf2,&__pyx_n_s_end1,&__pyx_n_s_end2,0};
+    PyObject* values[4] = {0,0,0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf1)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf2)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 1); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  2:
+        if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end1)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 2); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  3:
+        if (likely((values[3] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end2)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 3); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "two_fastq_heads") < 0)) __PYX_ERR(0, 69, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+    }
+    __pyx_v_buf1 = ((PyObject*)values[0]);
+    __pyx_v_buf2 = ((PyObject*)values[1]);
+    __pyx_v_end1 = __Pyx_PyIndex_AsSsize_t(values[2]); if (unlikely((__pyx_v_end1 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 69, __pyx_L3_error)
+    __pyx_v_end2 = __Pyx_PyIndex_AsSsize_t(values[3]); if (unlikely((__pyx_v_end2 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 69, __pyx_L3_error)
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 69, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.two_fastq_heads", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf1), (&PyBytes_Type), 1, "buf1", 1))) __PYX_ERR(0, 69, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf2), (&PyBytes_Type), 1, "buf2", 1))) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_18two_fastq_heads(__pyx_self, __pyx_v_buf1, __pyx_v_buf2, __pyx_v_end1, __pyx_v_end2);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_18two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf1, PyObject *__pyx_v_buf2, Py_ssize_t __pyx_v_end1, Py_ssize_t __pyx_v_end2) {
+  Py_ssize_t __pyx_v_pos1;
+  Py_ssize_t __pyx_v_pos2;
+  Py_ssize_t __pyx_v_linebreaks;
+  unsigned char *__pyx_v_data1;
+  unsigned char *__pyx_v_data2;
+  Py_ssize_t __pyx_v_record_start1;
+  Py_ssize_t __pyx_v_record_start2;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  unsigned char *__pyx_t_1;
+  int __pyx_t_2;
+  int __pyx_t_3;
+  PyObject *__pyx_t_4 = NULL;
+  PyObject *__pyx_t_5 = NULL;
+  PyObject *__pyx_t_6 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_0two_fastq_heads", 0);
+
+  __pyx_v_pos1 = 0;
+  __pyx_v_pos2 = 0;
+
+  __pyx_v_linebreaks = 0;
+
+  __pyx_t_1 = __Pyx_PyObject_AsUString(__pyx_v_buf1); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 80, __pyx_L1_error)
+  __pyx_v_data1 = __pyx_t_1;
+
+  __pyx_t_1 = __Pyx_PyObject_AsUString(__pyx_v_buf2); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 81, __pyx_L1_error)
+  __pyx_v_data2 = __pyx_t_1;
+
+  __pyx_v_record_start1 = 0;
+
+  __pyx_v_record_start2 = 0;
+
+  while (1) {
+
+    while (1) {
+      __pyx_t_3 = ((__pyx_v_pos1 < __pyx_v_end1) != 0);
+      if (__pyx_t_3) {
+      } else {
+        __pyx_t_2 = __pyx_t_3;
+        goto __pyx_L7_bool_binop_done;
+      }
+      __pyx_t_3 = (((__pyx_v_data1[__pyx_v_pos1]) != '\n') != 0);
+      __pyx_t_2 = __pyx_t_3;
+      __pyx_L7_bool_binop_done:;
+      if (!__pyx_t_2) break;
+
+      __pyx_v_pos1 = (__pyx_v_pos1 + 1);
+    }
+
+    __pyx_t_2 = ((__pyx_v_pos1 == __pyx_v_end1) != 0);
+    if (__pyx_t_2) {
+
+      goto __pyx_L4_break;
+
+    }
+
+    __pyx_v_pos1 = (__pyx_v_pos1 + 1);
+
+    while (1) {
+      __pyx_t_3 = ((__pyx_v_pos2 < __pyx_v_end2) != 0);
+      if (__pyx_t_3) {
+      } else {
+        __pyx_t_2 = __pyx_t_3;
+        goto __pyx_L12_bool_binop_done;
+      }
+      __pyx_t_3 = (((__pyx_v_data2[__pyx_v_pos2]) != '\n') != 0);
+      __pyx_t_2 = __pyx_t_3;
+      __pyx_L12_bool_binop_done:;
+      if (!__pyx_t_2) break;
+
+      __pyx_v_pos2 = (__pyx_v_pos2 + 1);
+    }
+
+    __pyx_t_2 = ((__pyx_v_pos2 == __pyx_v_end2) != 0);
+    if (__pyx_t_2) {
+
+      goto __pyx_L4_break;
+
+    }
+
+    __pyx_v_pos2 = (__pyx_v_pos2 + 1);
+
+    __pyx_v_linebreaks = (__pyx_v_linebreaks + 1);
+
+    __pyx_t_2 = ((__pyx_v_linebreaks == 4) != 0);
+    if (__pyx_t_2) {
+
+      __pyx_v_linebreaks = 0;
+
+      __pyx_v_record_start1 = __pyx_v_pos1;
+
+      __pyx_v_record_start2 = __pyx_v_pos2;
+
+    }
+  }
+  __pyx_L4_break:;
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_4 = PyInt_FromSsize_t(__pyx_v_record_start1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_4);
+  __pyx_t_5 = PyInt_FromSsize_t(__pyx_v_record_start2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_6);
+  __Pyx_GIVEREF(__pyx_t_4);
+  PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_4);
+  __Pyx_GIVEREF(__pyx_t_5);
+  PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_t_5);
+  __pyx_t_4 = 0;
+  __pyx_t_5 = 0;
+  __pyx_r = __pyx_t_6;
+  __pyx_t_6 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_4);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_6);
+  __Pyx_AddTraceback("cutadapt._seqio.two_fastq_heads", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+/* Python wrapper */
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_21two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_21two_fastq_heads = {"__pyx_fuse_1two_fastq_heads", (PyCFunction)__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_21two_fastq_heads, METH_VARARGS|METH_KEYWORDS, __pyx_doc_8cutadapt_6_seqio_4two_fastq_heads};
+static PyObject *__pyx_fuse_1__pyx_pw_8cutadapt_6_seqio_21two_fastq_heads(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_buf1 = 0;
+  PyObject *__pyx_v_buf2 = 0;
+  Py_ssize_t __pyx_v_end1;
+  Py_ssize_t __pyx_v_end2;
+  PyObject *__pyx_r = 0;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("two_fastq_heads (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_buf1,&__pyx_n_s_buf2,&__pyx_n_s_end1,&__pyx_n_s_end2,0};
+    PyObject* values[4] = {0,0,0,0};
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf1)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_buf2)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 1); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  2:
+        if (likely((values[2] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end1)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 2); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+        case  3:
+        if (likely((values[3] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_end2)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, 3); __PYX_ERR(0, 69, __pyx_L3_error)
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "two_fastq_heads") < 0)) __PYX_ERR(0, 69, __pyx_L3_error)
+      }
+    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
+      goto __pyx_L5_argtuple_error;
+    } else {
+      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+    }
+    __pyx_v_buf1 = ((PyObject*)values[0]);
+    __pyx_v_buf2 = ((PyObject*)values[1]);
+    __pyx_v_end1 = __Pyx_PyIndex_AsSsize_t(values[2]); if (unlikely((__pyx_v_end1 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 69, __pyx_L3_error)
+    __pyx_v_end2 = __Pyx_PyIndex_AsSsize_t(values[3]); if (unlikely((__pyx_v_end2 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 69, __pyx_L3_error)
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("two_fastq_heads", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 69, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.two_fastq_heads", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return NULL;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf1), (&PyByteArray_Type), 1, "buf1", 1))) __PYX_ERR(0, 69, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_buf2), (&PyByteArray_Type), 1, "buf2", 1))) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_20two_fastq_heads(__pyx_self, __pyx_v_buf1, __pyx_v_buf2, __pyx_v_end1, __pyx_v_end2);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+static PyObject *__pyx_pf_8cutadapt_6_seqio_20two_fastq_heads(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_buf1, PyObject *__pyx_v_buf2, Py_ssize_t __pyx_v_end1, Py_ssize_t __pyx_v_end2) {
+  Py_ssize_t __pyx_v_pos1;
+  Py_ssize_t __pyx_v_pos2;
+  Py_ssize_t __pyx_v_linebreaks;
+  unsigned char *__pyx_v_data1;
+  unsigned char *__pyx_v_data2;
+  Py_ssize_t __pyx_v_record_start1;
+  Py_ssize_t __pyx_v_record_start2;
+  PyObject *__pyx_r = NULL;
+  __Pyx_RefNannyDeclarations
+  unsigned char *__pyx_t_1;
+  int __pyx_t_2;
+  int __pyx_t_3;
+  PyObject *__pyx_t_4 = NULL;
+  PyObject *__pyx_t_5 = NULL;
+  PyObject *__pyx_t_6 = NULL;
+  __Pyx_RefNannySetupContext("__pyx_fuse_1two_fastq_heads", 0);
+
+  __pyx_v_pos1 = 0;
+  __pyx_v_pos2 = 0;
+
+  __pyx_v_linebreaks = 0;
+
+  __pyx_t_1 = __Pyx_PyObject_AsUString(__pyx_v_buf1); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 80, __pyx_L1_error)
+  __pyx_v_data1 = __pyx_t_1;
+
+  __pyx_t_1 = __Pyx_PyObject_AsUString(__pyx_v_buf2); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 81, __pyx_L1_error)
+  __pyx_v_data2 = __pyx_t_1;
+
+  __pyx_v_record_start1 = 0;
+
+  __pyx_v_record_start2 = 0;
+
+  while (1) {
+
+    while (1) {
+      __pyx_t_3 = ((__pyx_v_pos1 < __pyx_v_end1) != 0);
+      if (__pyx_t_3) {
+      } else {
+        __pyx_t_2 = __pyx_t_3;
+        goto __pyx_L7_bool_binop_done;
+      }
+      __pyx_t_3 = (((__pyx_v_data1[__pyx_v_pos1]) != '\n') != 0);
+      __pyx_t_2 = __pyx_t_3;
+      __pyx_L7_bool_binop_done:;
+      if (!__pyx_t_2) break;
+
+      __pyx_v_pos1 = (__pyx_v_pos1 + 1);
+    }
+
+    __pyx_t_2 = ((__pyx_v_pos1 == __pyx_v_end1) != 0);
+    if (__pyx_t_2) {
+
+      goto __pyx_L4_break;
+
+    }
+
+    __pyx_v_pos1 = (__pyx_v_pos1 + 1);
+
+    while (1) {
+      __pyx_t_3 = ((__pyx_v_pos2 < __pyx_v_end2) != 0);
+      if (__pyx_t_3) {
+      } else {
+        __pyx_t_2 = __pyx_t_3;
+        goto __pyx_L12_bool_binop_done;
+      }
+      __pyx_t_3 = (((__pyx_v_data2[__pyx_v_pos2]) != '\n') != 0);
+      __pyx_t_2 = __pyx_t_3;
+      __pyx_L12_bool_binop_done:;
+      if (!__pyx_t_2) break;
+
+      __pyx_v_pos2 = (__pyx_v_pos2 + 1);
+    }
+
+    __pyx_t_2 = ((__pyx_v_pos2 == __pyx_v_end2) != 0);
+    if (__pyx_t_2) {
+
+      goto __pyx_L4_break;
+
+    }
+
+    __pyx_v_pos2 = (__pyx_v_pos2 + 1);
+
+    __pyx_v_linebreaks = (__pyx_v_linebreaks + 1);
+
+    __pyx_t_2 = ((__pyx_v_linebreaks == 4) != 0);
+    if (__pyx_t_2) {
+
+      __pyx_v_linebreaks = 0;
+
+      __pyx_v_record_start1 = __pyx_v_pos1;
+
+      __pyx_v_record_start2 = __pyx_v_pos2;
+
+    }
+  }
+  __pyx_L4_break:;
+
+  __Pyx_XDECREF(__pyx_r);
+  __pyx_t_4 = PyInt_FromSsize_t(__pyx_v_record_start1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_4);
+  __pyx_t_5 = PyInt_FromSsize_t(__pyx_v_record_start2); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __pyx_t_6 = PyTuple_New(2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 103, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_6);
+  __Pyx_GIVEREF(__pyx_t_4);
+  PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_4);
+  __Pyx_GIVEREF(__pyx_t_5);
+  PyTuple_SET_ITEM(__pyx_t_6, 1, __pyx_t_5);
+  __pyx_t_4 = 0;
+  __pyx_t_5 = 0;
+  __pyx_r = __pyx_t_6;
+  __pyx_t_6 = 0;
+  goto __pyx_L0;
+
+
+  /* function exit code */
+  __pyx_L1_error:;
+  __Pyx_XDECREF(__pyx_t_4);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_6);
+  __Pyx_AddTraceback("cutadapt._seqio.two_fastq_heads", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __pyx_r = NULL;
+  __pyx_L0:;
+  __Pyx_XGIVEREF(__pyx_r);
+  __Pyx_RefNannyFinishContext();
+  return __pyx_r;
+}
+
+
+/* Python wrapper */
+static int __pyx_pw_8cutadapt_6_seqio_8Sequence_1__init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_8cutadapt_6_seqio_8Sequence___init__[] = "Set qualities to None if there are no quality values";
+#if CYTHON_COMPILING_IN_CPYTHON
+struct wrapperbase __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence___init__;
+#endif
+static int __pyx_pw_8cutadapt_6_seqio_8Sequence_1__init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+  PyObject *__pyx_v_name = 0;
+  PyObject *__pyx_v_sequence = 0;
+  PyObject *__pyx_v_qualities = 0;
+  int __pyx_v_second_header;
+  PyObject *__pyx_v_match = 0;
+  int __pyx_r;
+  __Pyx_RefNannyDeclarations
+  __Pyx_RefNannySetupContext("__init__ (wrapper)", 0);
+  {
+    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_name,&__pyx_n_s_sequence,&__pyx_n_s_qualities,&__pyx_n_s_second_header,&__pyx_n_s_match,0};
+    PyObject* values[5] = {0,0,0,0,0};
+    values[2] = ((PyObject*)Py_None);
+
+    values[4] = ((PyObject *)Py_None);
+    if (unlikely(__pyx_kwds)) {
+      Py_ssize_t kw_args;
+      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
+      switch (pos_args) {
+        case  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        case  0: break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+      kw_args = PyDict_Size(__pyx_kwds);
+      switch (pos_args) {
+        case  0:
+        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_name)) != 0)) kw_args--;
+        else goto __pyx_L5_argtuple_error;
+        case  1:
+        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_sequence)) != 0)) kw_args--;
+        else {
+          __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 5, 1); __PYX_ERR(0, 122, __pyx_L3_error)
+        }
+        case  2:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_qualities);
+          if (value) { values[2] = value; kw_args--; }
+        }
+        case  3:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_second_header);
+          if (value) { values[3] = value; kw_args--; }
+        }
+        case  4:
+        if (kw_args > 0) {
+          PyObject* value = PyDict_GetItem(__pyx_kwds, __pyx_n_s_match);
+          if (value) { values[4] = value; kw_args--; }
+        }
+      }
+      if (unlikely(kw_args > 0)) {
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__init__") < 0)) __PYX_ERR(0, 122, __pyx_L3_error)
+      }
+    } else {
+      switch (PyTuple_GET_SIZE(__pyx_args)) {
+        case  5: values[4] = PyTuple_GET_ITEM(__pyx_args, 4);
+        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
+        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
+        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
+        values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
+        break;
+        default: goto __pyx_L5_argtuple_error;
+      }
+    }
+    __pyx_v_name = ((PyObject*)values[0]);
+    __pyx_v_sequence = ((PyObject*)values[1]);
+    __pyx_v_qualities = ((PyObject*)values[2]);
+    if (values[3]) {
+      __pyx_v_second_header = __Pyx_PyObject_IsTrue(values[3]); if (unlikely((__pyx_v_second_header == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 122, __pyx_L3_error)
+    } else {
+
+      __pyx_v_second_header = ((int)0);
+    }
+    __pyx_v_match = values[4];
+  }
+  goto __pyx_L4_argument_unpacking_done;
+  __pyx_L5_argtuple_error:;
+  __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 122, __pyx_L3_error)
+  __pyx_L3_error:;
+  __Pyx_AddTraceback("cutadapt._seqio.Sequence.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_RefNannyFinishContext();
+  return -1;
+  __pyx_L4_argument_unpacking_done:;
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_name), (&PyString_Type), 1, "name", 1))) __PYX_ERR(0, 122, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_sequence), (&PyString_Type), 1, "sequence", 1))) __PYX_ERR(0, 122, __pyx_L1_error)
+  if (unlikely(!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_qualities), (&PyString_Type), 1, "qualities", 1))) __PYX_ERR(0, 122, __pyx_L1_error)
+  __pyx_r = __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(((struct __pyx_obj_8cutadapt_6_seqio_Sequence *)__pyx_v_self), __pyx_v_name, __pyx_v_sequence, __pyx_v_qualities, __pyx_v_second_header, __pyx_v_match);
+
+  /* function exit code */
+  goto __pyx_L0;
+  __pyx_L1_error:;
+  __pyx_r = -1;
   __pyx_L0:;
   __Pyx_RefNannyFinishContext();
   return __pyx_r;
@@ -1360,8 +3782,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutad
   PyObject *__pyx_t_10 = NULL;
   PyObject *__pyx_t_11 = NULL;
   PyObject *__pyx_t_12 = NULL;
-  int __pyx_t_13;
-  PyObject *__pyx_t_14 = NULL;
+  PyObject *__pyx_t_13 = NULL;
   __Pyx_RefNannySetupContext("__init__", 0);
 
   __Pyx_INCREF(__pyx_v_name);
@@ -1397,17 +3818,17 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutad
     __pyx_t_1 = __pyx_t_3;
     goto __pyx_L4_bool_binop_done;
   }
-  __pyx_t_4 = PyObject_Length(__pyx_v_qualities); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 32, __pyx_L1_error)
-  __pyx_t_5 = PyObject_Length(__pyx_v_sequence); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 32, __pyx_L1_error)
+  __pyx_t_4 = PyObject_Length(__pyx_v_qualities); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 130, __pyx_L1_error)
+  __pyx_t_5 = PyObject_Length(__pyx_v_sequence); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 130, __pyx_L1_error)
   __pyx_t_3 = ((__pyx_t_4 != __pyx_t_5) != 0);
   __pyx_t_1 = __pyx_t_3;
   __pyx_L4_bool_binop_done:;
   if (__pyx_t_1) {
 
-    __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 33, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 131, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_7);
     __pyx_t_8 = NULL;
-    if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_7))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_7))) {
       __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_7);
       if (likely(__pyx_t_8)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_7);
@@ -1417,107 +3838,67 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutad
       }
     }
     if (!__pyx_t_8) {
-      __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_v_name); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error)
+      __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_v_name); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 131, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_6);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_7)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_name};
-        __pyx_t_6 = __Pyx_PyFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_6);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_7)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_v_name};
-        __pyx_t_6 = __Pyx_PyCFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_6);
-      } else
-      #endif
-      {
-        __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 33, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_9);
-        __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
-        __Pyx_INCREF(__pyx_v_name);
-        __Pyx_GIVEREF(__pyx_v_name);
-        PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_name);
-        __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 33, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      }
+      __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 131, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_9);
+      __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
+      __Pyx_INCREF(__pyx_v_name);
+      __Pyx_GIVEREF(__pyx_v_name);
+      PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_name);
+      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 131, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_6);
+      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
     }
     __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
     __pyx_v_rname = __pyx_t_6;
     __pyx_t_6 = 0;
 
-    __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 34, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 132, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_7);
 
-    __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_In_read_named_0_r_length_of_qual, __pyx_n_s_format); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 35, __pyx_L1_error)
+    __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_In_read_named_0_r_length_of_qual, __pyx_n_s_format); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 133, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
 
-    __pyx_t_5 = PyObject_Length(__pyx_v_qualities); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 36, __pyx_L1_error)
-    __pyx_t_10 = PyInt_FromSsize_t(__pyx_t_5); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 36, __pyx_L1_error)
+    __pyx_t_5 = PyObject_Length(__pyx_v_qualities); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 134, __pyx_L1_error)
+    __pyx_t_10 = PyInt_FromSsize_t(__pyx_t_5); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 134, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_10);
-    __pyx_t_5 = PyObject_Length(__pyx_v_sequence); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 36, __pyx_L1_error)
-    __pyx_t_11 = PyInt_FromSsize_t(__pyx_t_5); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 36, __pyx_L1_error)
+    __pyx_t_5 = PyObject_Length(__pyx_v_sequence); if (unlikely(__pyx_t_5 == -1)) __PYX_ERR(0, 134, __pyx_L1_error)
+    __pyx_t_11 = PyInt_FromSsize_t(__pyx_t_5); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 134, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_11);
     __pyx_t_12 = NULL;
-    __pyx_t_13 = 0;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_8))) {
+    __pyx_t_5 = 0;
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_8))) {
       __pyx_t_12 = PyMethod_GET_SELF(__pyx_t_8);
       if (likely(__pyx_t_12)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
         __Pyx_INCREF(__pyx_t_12);
         __Pyx_INCREF(function);
         __Pyx_DECREF_SET(__pyx_t_8, function);
-        __pyx_t_13 = 1;
-      }
-    }
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_8)) {
-      PyObject *__pyx_temp[4] = {__pyx_t_12, __pyx_v_rname, __pyx_t_10, __pyx_t_11};
-      __pyx_t_9 = __Pyx_PyFunction_FastCall(__pyx_t_8, __pyx_temp+1-__pyx_t_13, 3+__pyx_t_13); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 35, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_12); __pyx_t_12 = 0;
-      __Pyx_GOTREF(__pyx_t_9);
-      __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
-      __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_8)) {
-      PyObject *__pyx_temp[4] = {__pyx_t_12, __pyx_v_rname, __pyx_t_10, __pyx_t_11};
-      __pyx_t_9 = __Pyx_PyCFunction_FastCall(__pyx_t_8, __pyx_temp+1-__pyx_t_13, 3+__pyx_t_13); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 35, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_12); __pyx_t_12 = 0;
-      __Pyx_GOTREF(__pyx_t_9);
-      __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
-      __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
-    } else
-    #endif
-    {
-      __pyx_t_14 = PyTuple_New(3+__pyx_t_13); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 35, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_14);
-      if (__pyx_t_12) {
-        __Pyx_GIVEREF(__pyx_t_12); PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_t_12); __pyx_t_12 = NULL;
+        __pyx_t_5 = 1;
       }
-      __Pyx_INCREF(__pyx_v_rname);
-      __Pyx_GIVEREF(__pyx_v_rname);
-      PyTuple_SET_ITEM(__pyx_t_14, 0+__pyx_t_13, __pyx_v_rname);
-      __Pyx_GIVEREF(__pyx_t_10);
-      PyTuple_SET_ITEM(__pyx_t_14, 1+__pyx_t_13, __pyx_t_10);
-      __Pyx_GIVEREF(__pyx_t_11);
-      PyTuple_SET_ITEM(__pyx_t_14, 2+__pyx_t_13, __pyx_t_11);
-      __pyx_t_10 = 0;
-      __pyx_t_11 = 0;
-      __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_8, __pyx_t_14, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 35, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_9);
-      __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
     }
+    __pyx_t_13 = PyTuple_New(3+__pyx_t_5); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 133, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_13);
+    if (__pyx_t_12) {
+      __Pyx_GIVEREF(__pyx_t_12); PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_t_12); __pyx_t_12 = NULL;
+    }
+    __Pyx_INCREF(__pyx_v_rname);
+    __Pyx_GIVEREF(__pyx_v_rname);
+    PyTuple_SET_ITEM(__pyx_t_13, 0+__pyx_t_5, __pyx_v_rname);
+    __Pyx_GIVEREF(__pyx_t_10);
+    PyTuple_SET_ITEM(__pyx_t_13, 1+__pyx_t_5, __pyx_t_10);
+    __Pyx_GIVEREF(__pyx_t_11);
+    PyTuple_SET_ITEM(__pyx_t_13, 2+__pyx_t_5, __pyx_t_11);
+    __pyx_t_10 = 0;
+    __pyx_t_11 = 0;
+    __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_8, __pyx_t_13, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 133, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0;
     __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     __pyx_t_8 = NULL;
-    if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_7))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_7))) {
       __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_7);
       if (likely(__pyx_t_8)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_7);
@@ -1527,44 +3908,24 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutad
       }
     }
     if (!__pyx_t_8) {
-      __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_t_9); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 34, __pyx_L1_error)
+      __pyx_t_6 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_t_9); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 132, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
       __Pyx_GOTREF(__pyx_t_6);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_7)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_t_9};
-        __pyx_t_6 = __Pyx_PyFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 34, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_7)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_8, __pyx_t_9};
-        __pyx_t_6 = __Pyx_PyCFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 34, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-      } else
-      #endif
-      {
-        __pyx_t_14 = PyTuple_New(1+1); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 34, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_14);
-        __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_t_8); __pyx_t_8 = NULL;
-        __Pyx_GIVEREF(__pyx_t_9);
-        PyTuple_SET_ITEM(__pyx_t_14, 0+1, __pyx_t_9);
-        __pyx_t_9 = 0;
-        __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_14, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 34, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
-      }
+      __pyx_t_13 = PyTuple_New(1+1); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 132, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_13);
+      __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_13, 0, __pyx_t_8); __pyx_t_8 = NULL;
+      __Pyx_GIVEREF(__pyx_t_9);
+      PyTuple_SET_ITEM(__pyx_t_13, 0+1, __pyx_t_9);
+      __pyx_t_9 = 0;
+      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_13, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 132, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_6);
+      __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0;
     }
     __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
     __Pyx_Raise(__pyx_t_6, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-    __PYX_ERR(0, 34, __pyx_L1_error)
+    __PYX_ERR(0, 132, __pyx_L1_error)
 
   }
 
@@ -1580,7 +3941,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence___init__(struct __pyx_obj_8cutad
   __Pyx_XDECREF(__pyx_t_10);
   __Pyx_XDECREF(__pyx_t_11);
   __Pyx_XDECREF(__pyx_t_12);
-  __Pyx_XDECREF(__pyx_t_14);
+  __Pyx_XDECREF(__pyx_t_13);
   __Pyx_AddTraceback("cutadapt._seqio.Sequence.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __pyx_r = -1;
   __pyx_L0:;
@@ -1617,20 +3978,20 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_2__getitem__(struct __pyx_
   int __pyx_t_5;
   PyObject *__pyx_t_6 = NULL;
   PyObject *__pyx_t_7 = NULL;
-  int __pyx_t_8;
+  Py_ssize_t __pyx_t_8;
   PyObject *__pyx_t_9 = NULL;
   __Pyx_RefNannySetupContext("__getitem__", 0);
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_class); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 40, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_class); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 138, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
 
-  __pyx_t_3 = PyObject_GetItem(__pyx_v_self->sequence, __pyx_v_key); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 42, __pyx_L1_error)
+  __pyx_t_3 = PyObject_GetItem(__pyx_v_self->sequence, __pyx_v_key); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 140, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
 
   __pyx_t_5 = (__pyx_v_self->qualities != ((PyObject*)Py_None));
   if ((__pyx_t_5 != 0)) {
-    __pyx_t_6 = PyObject_GetItem(__pyx_v_self->qualities, __pyx_v_key); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 43, __pyx_L1_error)
+    __pyx_t_6 = PyObject_GetItem(__pyx_v_self->qualities, __pyx_v_key); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 141, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
     __pyx_t_4 = __pyx_t_6;
     __pyx_t_6 = 0;
@@ -1639,12 +4000,12 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_2__getitem__(struct __pyx_
     __pyx_t_4 = Py_None;
   }
 
-  __pyx_t_6 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 44, __pyx_L1_error)
+  __pyx_t_6 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 142, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_6);
 
   __pyx_t_7 = NULL;
   __pyx_t_8 = 0;
-  if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_2))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
     __pyx_t_7 = PyMethod_GET_SELF(__pyx_t_2);
     if (likely(__pyx_t_7)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
@@ -1654,53 +4015,29 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_2__getitem__(struct __pyx_
       __pyx_t_8 = 1;
     }
   }
-  #if CYTHON_FAST_PYCALL
-  if (PyFunction_Check(__pyx_t_2)) {
-    PyObject *__pyx_temp[6] = {__pyx_t_7, __pyx_v_self->name, __pyx_t_3, __pyx_t_4, __pyx_t_6, __pyx_v_self->match};
-    __pyx_t_1 = __Pyx_PyFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_8, 5+__pyx_t_8); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 40, __pyx_L1_error)
-    __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-    __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-  } else
-  #endif
-  #if CYTHON_FAST_PYCCALL
-  if (__Pyx_PyFastCFunction_Check(__pyx_t_2)) {
-    PyObject *__pyx_temp[6] = {__pyx_t_7, __pyx_v_self->name, __pyx_t_3, __pyx_t_4, __pyx_t_6, __pyx_v_self->match};
-    __pyx_t_1 = __Pyx_PyCFunction_FastCall(__pyx_t_2, __pyx_temp+1-__pyx_t_8, 5+__pyx_t_8); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 40, __pyx_L1_error)
-    __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-    __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-  } else
-  #endif
-  {
-    __pyx_t_9 = PyTuple_New(5+__pyx_t_8); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 40, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_9);
-    if (__pyx_t_7) {
-      __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_7); __pyx_t_7 = NULL;
-    }
-    __Pyx_INCREF(__pyx_v_self->name);
-    __Pyx_GIVEREF(__pyx_v_self->name);
-    PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_8, __pyx_v_self->name);
-    __Pyx_GIVEREF(__pyx_t_3);
-    PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_8, __pyx_t_3);
-    __Pyx_GIVEREF(__pyx_t_4);
-    PyTuple_SET_ITEM(__pyx_t_9, 2+__pyx_t_8, __pyx_t_4);
-    __Pyx_GIVEREF(__pyx_t_6);
-    PyTuple_SET_ITEM(__pyx_t_9, 3+__pyx_t_8, __pyx_t_6);
-    __Pyx_INCREF(__pyx_v_self->match);
-    __Pyx_GIVEREF(__pyx_v_self->match);
-    PyTuple_SET_ITEM(__pyx_t_9, 4+__pyx_t_8, __pyx_v_self->match);
-    __pyx_t_3 = 0;
-    __pyx_t_4 = 0;
-    __pyx_t_6 = 0;
-    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_9, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 40, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_1);
-    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+  __pyx_t_9 = PyTuple_New(5+__pyx_t_8); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 138, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_9);
+  if (__pyx_t_7) {
+    __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_7); __pyx_t_7 = NULL;
   }
+  __Pyx_INCREF(__pyx_v_self->name);
+  __Pyx_GIVEREF(__pyx_v_self->name);
+  PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_8, __pyx_v_self->name);
+  __Pyx_GIVEREF(__pyx_t_3);
+  PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_8, __pyx_t_3);
+  __Pyx_GIVEREF(__pyx_t_4);
+  PyTuple_SET_ITEM(__pyx_t_9, 2+__pyx_t_8, __pyx_t_4);
+  __Pyx_GIVEREF(__pyx_t_6);
+  PyTuple_SET_ITEM(__pyx_t_9, 3+__pyx_t_8, __pyx_t_6);
+  __Pyx_INCREF(__pyx_v_self->match);
+  __Pyx_GIVEREF(__pyx_v_self->match);
+  PyTuple_SET_ITEM(__pyx_t_9, 4+__pyx_t_8, __pyx_v_self->match);
+  __pyx_t_3 = 0;
+  __pyx_t_4 = 0;
+  __pyx_t_6 = 0;
+  __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_9, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 138, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_1);
+  __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   __pyx_r = __pyx_t_1;
   __pyx_t_1 = 0;
@@ -1751,22 +4088,22 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
   PyObject *__pyx_t_7 = NULL;
   PyObject *__pyx_t_8 = NULL;
   PyObject *__pyx_t_9 = NULL;
-  int __pyx_t_10;
+  Py_ssize_t __pyx_t_10;
   __Pyx_RefNannySetupContext("__repr__", 0);
 
-  __Pyx_INCREF(__pyx_kp_s_);
-  __pyx_v_qstr = __pyx_kp_s_;
+  __Pyx_INCREF(__pyx_kp_s__16);
+  __pyx_v_qstr = __pyx_kp_s__16;
 
   __pyx_t_1 = (__pyx_v_self->qualities != ((PyObject*)Py_None));
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
-    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_qualities_0_r, __pyx_n_s_format); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 50, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_qualities_0_r, __pyx_n_s_format); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 148, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
-    __pyx_t_6 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 50, __pyx_L1_error)
+    __pyx_t_6 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 148, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
     __pyx_t_7 = NULL;
-    if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_6))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_6))) {
       __pyx_t_7 = PyMethod_GET_SELF(__pyx_t_6);
       if (likely(__pyx_t_7)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6);
@@ -1776,40 +4113,22 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
       }
     }
     if (!__pyx_t_7) {
-      __pyx_t_5 = __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_v_self->qualities); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 50, __pyx_L1_error)
+      __pyx_t_5 = __Pyx_PyObject_CallOneArg(__pyx_t_6, __pyx_v_self->qualities); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 148, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_5);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_6)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_7, __pyx_v_self->qualities};
-        __pyx_t_5 = __Pyx_PyFunction_FastCall(__pyx_t_6, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-        __Pyx_GOTREF(__pyx_t_5);
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_6)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_7, __pyx_v_self->qualities};
-        __pyx_t_5 = __Pyx_PyCFunction_FastCall(__pyx_t_6, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-        __Pyx_GOTREF(__pyx_t_5);
-      } else
-      #endif
-      {
-        __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_7); __pyx_t_7 = NULL;
-        __Pyx_INCREF(__pyx_v_self->qualities);
-        __Pyx_GIVEREF(__pyx_v_self->qualities);
-        PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_self->qualities);
-        __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_5);
-        __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      }
+      __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 148, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_7); __pyx_t_7 = NULL;
+      __Pyx_INCREF(__pyx_v_self->qualities);
+      __Pyx_GIVEREF(__pyx_v_self->qualities);
+      PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_v_self->qualities);
+      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 148, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     }
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     __pyx_t_6 = NULL;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_4))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_4))) {
       __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_4);
       if (likely(__pyx_t_6)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4);
@@ -1819,39 +4138,19 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
       }
     }
     if (!__pyx_t_6) {
-      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_5); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 50, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_t_4, __pyx_t_5); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 148, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
       __Pyx_GOTREF(__pyx_t_3);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_4)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-        __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_4, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_4)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-        __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_4, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      } else
-      #endif
-      {
-        __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_8);
-        __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_6); __pyx_t_6 = NULL;
-        __Pyx_GIVEREF(__pyx_t_5);
-        PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_t_5);
-        __pyx_t_5 = 0;
-        __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_8, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 50, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_3);
-        __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      }
+      __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 148, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_6); __pyx_t_6 = NULL;
+      __Pyx_GIVEREF(__pyx_t_5);
+      PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_t_5);
+      __pyx_t_5 = 0;
+      __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_8, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 148, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_3);
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
     }
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_DECREF_SET(__pyx_v_qstr, __pyx_t_3);
@@ -1860,12 +4159,12 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
   }
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Sequence_name_0_r_sequence_1_r, __pyx_n_s_format); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 51, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Sequence_name_0_r_sequence_1_r, __pyx_n_s_format); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 149, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
-  __pyx_t_5 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 51, __pyx_L1_error)
+  __pyx_t_5 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 149, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_5);
   __pyx_t_6 = NULL;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_5))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_5))) {
     __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_5);
     if (likely(__pyx_t_6)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
@@ -1875,42 +4174,24 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
     }
   }
   if (!__pyx_t_6) {
-    __pyx_t_8 = __Pyx_PyObject_CallOneArg(__pyx_t_5, __pyx_v_self->name); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 51, __pyx_L1_error)
+    __pyx_t_8 = __Pyx_PyObject_CallOneArg(__pyx_t_5, __pyx_v_self->name); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 149, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
   } else {
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_5)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_self->name};
-      __pyx_t_8 = __Pyx_PyFunction_FastCall(__pyx_t_5, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-      __Pyx_GOTREF(__pyx_t_8);
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_5)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_self->name};
-      __pyx_t_8 = __Pyx_PyCFunction_FastCall(__pyx_t_5, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-      __Pyx_GOTREF(__pyx_t_8);
-    } else
-    #endif
-    {
-      __pyx_t_7 = PyTuple_New(1+1); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_7);
-      __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_7, 0, __pyx_t_6); __pyx_t_6 = NULL;
-      __Pyx_INCREF(__pyx_v_self->name);
-      __Pyx_GIVEREF(__pyx_v_self->name);
-      PyTuple_SET_ITEM(__pyx_t_7, 0+1, __pyx_v_self->name);
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_7, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_8);
-      __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-    }
+    __pyx_t_7 = PyTuple_New(1+1); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 149, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_7);
+    __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_7, 0, __pyx_t_6); __pyx_t_6 = NULL;
+    __Pyx_INCREF(__pyx_v_self->name);
+    __Pyx_GIVEREF(__pyx_v_self->name);
+    PyTuple_SET_ITEM(__pyx_t_7, 0+1, __pyx_v_self->name);
+    __pyx_t_8 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_7, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 149, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_8);
+    __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
   }
   __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-  __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 51, __pyx_L1_error)
+  __pyx_t_7 = __Pyx_GetModuleGlobalName(__pyx_n_s_shorten); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 149, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_7);
   __pyx_t_6 = NULL;
-  if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_7))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_7))) {
     __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_7);
     if (likely(__pyx_t_6)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_7);
@@ -1920,41 +4201,23 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
     }
   }
   if (!__pyx_t_6) {
-    __pyx_t_5 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_v_self->sequence); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 51, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_CallOneArg(__pyx_t_7, __pyx_v_self->sequence); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 149, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
   } else {
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_7)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_self->sequence};
-      __pyx_t_5 = __Pyx_PyFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-      __Pyx_GOTREF(__pyx_t_5);
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_7)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_v_self->sequence};
-      __pyx_t_5 = __Pyx_PyCFunction_FastCall(__pyx_t_7, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-      __Pyx_GOTREF(__pyx_t_5);
-    } else
-    #endif
-    {
-      __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_9);
-      __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_6); __pyx_t_6 = NULL;
-      __Pyx_INCREF(__pyx_v_self->sequence);
-      __Pyx_GIVEREF(__pyx_v_self->sequence);
-      PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_self->sequence);
-      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 51, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_5);
-      __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-    }
+    __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 149, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_9);
+    __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_6); __pyx_t_6 = NULL;
+    __Pyx_INCREF(__pyx_v_self->sequence);
+    __Pyx_GIVEREF(__pyx_v_self->sequence);
+    PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_v_self->sequence);
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_7, __pyx_t_9, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 149, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   }
   __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
   __pyx_t_7 = NULL;
   __pyx_t_10 = 0;
-  if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_4))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_4))) {
     __pyx_t_7 = PyMethod_GET_SELF(__pyx_t_4);
     if (likely(__pyx_t_7)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4);
@@ -1964,45 +4227,23 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_4__repr__(struct __pyx_obj
       __pyx_t_10 = 1;
     }
   }
-  #if CYTHON_FAST_PYCALL
-  if (PyFunction_Check(__pyx_t_4)) {
-    PyObject *__pyx_temp[4] = {__pyx_t_7, __pyx_t_8, __pyx_t_5, __pyx_v_qstr};
-    __pyx_t_3 = __Pyx_PyFunction_FastCall(__pyx_t_4, __pyx_temp+1-__pyx_t_10, 3+__pyx_t_10); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 51, __pyx_L1_error)
-    __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-    __Pyx_GOTREF(__pyx_t_3);
-    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-  } else
-  #endif
-  #if CYTHON_FAST_PYCCALL
-  if (__Pyx_PyFastCFunction_Check(__pyx_t_4)) {
-    PyObject *__pyx_temp[4] = {__pyx_t_7, __pyx_t_8, __pyx_t_5, __pyx_v_qstr};
-    __pyx_t_3 = __Pyx_PyCFunction_FastCall(__pyx_t_4, __pyx_temp+1-__pyx_t_10, 3+__pyx_t_10); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 51, __pyx_L1_error)
-    __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-    __Pyx_GOTREF(__pyx_t_3);
-    __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-  } else
-  #endif
-  {
-    __pyx_t_9 = PyTuple_New(3+__pyx_t_10); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 51, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_9);
-    if (__pyx_t_7) {
-      __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_7); __pyx_t_7 = NULL;
-    }
-    __Pyx_GIVEREF(__pyx_t_8);
-    PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_10, __pyx_t_8);
-    __Pyx_GIVEREF(__pyx_t_5);
-    PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_10, __pyx_t_5);
-    __Pyx_INCREF(__pyx_v_qstr);
-    __Pyx_GIVEREF(__pyx_v_qstr);
-    PyTuple_SET_ITEM(__pyx_t_9, 2+__pyx_t_10, __pyx_v_qstr);
-    __pyx_t_8 = 0;
-    __pyx_t_5 = 0;
-    __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_9, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 51, __pyx_L1_error)
-    __Pyx_GOTREF(__pyx_t_3);
-    __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+  __pyx_t_9 = PyTuple_New(3+__pyx_t_10); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 149, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_9);
+  if (__pyx_t_7) {
+    __Pyx_GIVEREF(__pyx_t_7); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_7); __pyx_t_7 = NULL;
   }
+  __Pyx_GIVEREF(__pyx_t_8);
+  PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_10, __pyx_t_8);
+  __Pyx_GIVEREF(__pyx_t_5);
+  PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_10, __pyx_t_5);
+  __Pyx_INCREF(__pyx_v_qstr);
+  __Pyx_GIVEREF(__pyx_v_qstr);
+  PyTuple_SET_ITEM(__pyx_t_9, 2+__pyx_t_10, __pyx_v_qstr);
+  __pyx_t_8 = 0;
+  __pyx_t_5 = 0;
+  __pyx_t_3 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_9, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 149, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_3);
+  __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
   __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
   __pyx_r = __pyx_t_3;
   __pyx_t_3 = 0;
@@ -2050,7 +4291,7 @@ static Py_ssize_t __pyx_pf_8cutadapt_6_seqio_8Sequence_6__len__(struct __pyx_obj
 
   __pyx_t_1 = __pyx_v_self->sequence;
   __Pyx_INCREF(__pyx_t_1);
-  __pyx_t_2 = PyObject_Length(__pyx_t_1); if (unlikely(__pyx_t_2 == -1)) __PYX_ERR(0, 54, __pyx_L1_error)
+  __pyx_t_2 = PyObject_Length(__pyx_t_1); if (unlikely(__pyx_t_2 == -1)) __PYX_ERR(0, 152, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __pyx_r = __pyx_t_2;
   goto __pyx_L0;
@@ -2099,14 +4340,14 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_8__richcmp__(PyObject *__p
   __pyx_t_2 = (__pyx_t_1 != 0);
   if (__pyx_t_2) {
 
-    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_name); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 58, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_name); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 156, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
-    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_name); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_name); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 156, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
-    __pyx_t_6 = PyObject_RichCompare(__pyx_t_4, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_6); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 58, __pyx_L1_error)
+    __pyx_t_6 = PyObject_RichCompare(__pyx_t_4, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_6); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 156, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_6); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 58, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_6); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 156, __pyx_L1_error)
     if (__pyx_t_2) {
       __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     } else {
@@ -2116,14 +4357,14 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_8__richcmp__(PyObject *__p
       goto __pyx_L4_bool_binop_done;
     }
 
-    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_sequence); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 59, __pyx_L1_error)
+    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_sequence); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 157, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
-    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_sequence); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 59, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_sequence); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 157, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
-    __pyx_t_4 = PyObject_RichCompare(__pyx_t_6, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 59, __pyx_L1_error)
+    __pyx_t_4 = PyObject_RichCompare(__pyx_t_6, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 157, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-    __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 59, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 157, __pyx_L1_error)
     if (__pyx_t_2) {
       __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
     } else {
@@ -2133,11 +4374,11 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_8__richcmp__(PyObject *__p
       goto __pyx_L4_bool_binop_done;
     }
 
-    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_qualities); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L1_error)
+    __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_v_self, __pyx_n_s_qualities); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 158, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_4);
-    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_qualities); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 60, __pyx_L1_error)
+    __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_v_other, __pyx_n_s_qualities); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 158, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_5);
-    __pyx_t_6 = PyObject_RichCompare(__pyx_t_4, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_6); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 60, __pyx_L1_error)
+    __pyx_t_6 = PyObject_RichCompare(__pyx_t_4, __pyx_t_5, Py_EQ); __Pyx_XGOTREF(__pyx_t_6); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 158, __pyx_L1_error)
     __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
     __Pyx_INCREF(__pyx_t_6);
@@ -2159,8 +4400,8 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_8__richcmp__(PyObject *__p
 
     /*else*/ {
       __Pyx_XDECREF(__pyx_r);
-      __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_v_eq); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 64, __pyx_L1_error)
-      __pyx_t_3 = __Pyx_PyBool_FromLong((!__pyx_t_2)); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 64, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_v_eq); if (unlikely(__pyx_t_2 < 0)) __PYX_ERR(0, 162, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_PyBool_FromLong((!__pyx_t_2)); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 162, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_3);
       __pyx_r = __pyx_t_3;
       __pyx_t_3 = 0;
@@ -2170,11 +4411,11 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_8__richcmp__(PyObject *__p
   }
 
   /*else*/ {
-    __pyx_t_3 = __Pyx_PyObject_CallNoArg(__pyx_builtin_NotImplementedError); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 66, __pyx_L1_error)
+    __pyx_t_3 = __Pyx_PyObject_CallNoArg(__pyx_builtin_NotImplementedError); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 164, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_3);
     __Pyx_Raise(__pyx_t_3, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-    __PYX_ERR(0, 66, __pyx_L1_error)
+    __PYX_ERR(0, 164, __pyx_L1_error)
   }
 
 
@@ -2215,10 +4456,10 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_10__reduce__(struct __pyx_
   __Pyx_RefNannySetupContext("__reduce__", 0);
 
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_1 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 167, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
 
-  __pyx_t_2 = PyTuple_New(5); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_2 = PyTuple_New(5); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 167, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_INCREF(__pyx_v_self->name);
   __Pyx_GIVEREF(__pyx_v_self->name);
@@ -2236,7 +4477,7 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_10__reduce__(struct __pyx_
   PyTuple_SET_ITEM(__pyx_t_2, 4, __pyx_v_self->match);
   __pyx_t_1 = 0;
 
-  __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __pyx_t_1 = PyTuple_New(2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 167, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __Pyx_INCREF(((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence));
   __Pyx_GIVEREF(((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence));
@@ -2309,7 +4550,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_4name_2__set__(struct __pyx_obj_
   __Pyx_RefNannyDeclarations
   PyObject *__pyx_t_1 = NULL;
   __Pyx_RefNannySetupContext("__set__", 0);
-  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 18, __pyx_L1_error)
+  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 116, __pyx_L1_error)
   __pyx_t_1 = __pyx_v_value;
   __Pyx_INCREF(__pyx_t_1);
   __Pyx_GIVEREF(__pyx_t_1);
@@ -2407,7 +4648,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_8sequence_2__set__(struct __pyx_
   __Pyx_RefNannyDeclarations
   PyObject *__pyx_t_1 = NULL;
   __Pyx_RefNannySetupContext("__set__", 0);
-  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 19, __pyx_L1_error)
+  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 117, __pyx_L1_error)
   __pyx_t_1 = __pyx_v_value;
   __Pyx_INCREF(__pyx_t_1);
   __Pyx_GIVEREF(__pyx_t_1);
@@ -2505,7 +4746,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_9qualities_2__set__(struct __pyx
   __Pyx_RefNannyDeclarations
   PyObject *__pyx_t_1 = NULL;
   __Pyx_RefNannySetupContext("__set__", 0);
-  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 20, __pyx_L1_error)
+  if (!(likely(PyString_CheckExact(__pyx_v_value))||((__pyx_v_value) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_v_value)->tp_name), 0))) __PYX_ERR(0, 118, __pyx_L1_error)
   __pyx_t_1 = __pyx_v_value;
   __Pyx_INCREF(__pyx_t_1);
   __Pyx_GIVEREF(__pyx_t_1);
@@ -2575,7 +4816,7 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_8Sequence_13second_header___get__(st
   PyObject *__pyx_t_1 = NULL;
   __Pyx_RefNannySetupContext("__get__", 0);
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_1 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 21, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyBool_FromLong(__pyx_v_self->second_header); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 119, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __pyx_r = __pyx_t_1;
   __pyx_t_1 = 0;
@@ -2610,7 +4851,7 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_13second_header_2__set__(struct
   __Pyx_RefNannyDeclarations
   int __pyx_t_1;
   __Pyx_RefNannySetupContext("__set__", 0);
-  __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_v_value); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 21, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_v_value); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 119, __pyx_L1_error)
   __pyx_v_self->second_header = __pyx_t_1;
 
   /* function exit code */
@@ -2713,19 +4954,19 @@ static int __pyx_pf_8cutadapt_6_seqio_8Sequence_5match_4__del__(struct __pyx_obj
 }
 
 
-static PyObject *__pyx_pf_8cutadapt_6_seqio___defaults__(CYTHON_UNUSED PyObject *__pyx_self) {
+static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader_5__defaults__(CYTHON_UNUSED PyObject *__pyx_self) {
   PyObject *__pyx_r = NULL;
   __Pyx_RefNannyDeclarations
   PyObject *__pyx_t_1 = NULL;
   PyObject *__pyx_t_2 = NULL;
   __Pyx_RefNannySetupContext("__defaults__", 0);
   __Pyx_XDECREF(__pyx_r);
-  __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 77, __pyx_L1_error)
+  __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 175, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __Pyx_INCREF(__Pyx_CyFunction_Defaults(__pyx_defaults, __pyx_self)->__pyx_arg_sequence_class);
-  __Pyx_GIVEREF(__Pyx_CyFunction_Defaults(__pyx_defaults, __pyx_self)->__pyx_arg_sequence_class);
-  PyTuple_SET_ITEM(__pyx_t_1, 0, __Pyx_CyFunction_Defaults(__pyx_defaults, __pyx_self)->__pyx_arg_sequence_class);
-  __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 77, __pyx_L1_error)
+  __Pyx_INCREF(__Pyx_CyFunction_Defaults(__pyx_defaults4, __pyx_self)->__pyx_arg_sequence_class);
+  __Pyx_GIVEREF(__Pyx_CyFunction_Defaults(__pyx_defaults4, __pyx_self)->__pyx_arg_sequence_class);
+  PyTuple_SET_ITEM(__pyx_t_1, 0, __Pyx_CyFunction_Defaults(__pyx_defaults4, __pyx_self)->__pyx_arg_sequence_class);
+  __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 175, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_GIVEREF(__pyx_t_1);
   PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_1);
@@ -2741,7 +4982,7 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio___defaults__(CYTHON_UNUSED PyObject
   __pyx_L1_error:;
   __Pyx_XDECREF(__pyx_t_1);
   __Pyx_XDECREF(__pyx_t_2);
-  __Pyx_AddTraceback("cutadapt._seqio.__defaults__", __pyx_clineno, __pyx_lineno, __pyx_filename);
+  __Pyx_AddTraceback("cutadapt._seqio.FastqReader.__defaults__", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __pyx_r = NULL;
   __pyx_L0:;
   __Pyx_XGIVEREF(__pyx_r);
@@ -2763,7 +5004,7 @@ static PyObject *__pyx_pw_8cutadapt_6_seqio_11FastqReader_1__init__(PyObject *__
   {
     static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_self,&__pyx_n_s_file,&__pyx_n_s_sequence_class,0};
     PyObject* values[3] = {0,0,0};
-    __pyx_defaults *__pyx_dynamic_args = __Pyx_CyFunction_Defaults(__pyx_defaults, __pyx_self);
+    __pyx_defaults4 *__pyx_dynamic_args = __Pyx_CyFunction_Defaults(__pyx_defaults4, __pyx_self);
     values[2] = __pyx_dynamic_args->__pyx_arg_sequence_class;
     if (unlikely(__pyx_kwds)) {
       Py_ssize_t kw_args;
@@ -2783,7 +5024,7 @@ static PyObject *__pyx_pw_8cutadapt_6_seqio_11FastqReader_1__init__(PyObject *__
         case  1:
         if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_file)) != 0)) kw_args--;
         else {
-          __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 3, 1); __PYX_ERR(0, 77, __pyx_L3_error)
+          __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 3, 1); __PYX_ERR(0, 175, __pyx_L3_error)
         }
         case  2:
         if (kw_args > 0) {
@@ -2792,7 +5033,7 @@ static PyObject *__pyx_pw_8cutadapt_6_seqio_11FastqReader_1__init__(PyObject *__
         }
       }
       if (unlikely(kw_args > 0)) {
-        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__init__") < 0)) __PYX_ERR(0, 77, __pyx_L3_error)
+        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__init__") < 0)) __PYX_ERR(0, 175, __pyx_L3_error)
       }
     } else {
       switch (PyTuple_GET_SIZE(__pyx_args)) {
@@ -2809,7 +5050,7 @@ static PyObject *__pyx_pw_8cutadapt_6_seqio_11FastqReader_1__init__(PyObject *__
   }
   goto __pyx_L4_argument_unpacking_done;
   __pyx_L5_argtuple_error:;
-  __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 77, __pyx_L3_error)
+  __Pyx_RaiseArgtupleInvalid("__init__", 0, 2, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 175, __pyx_L3_error)
   __pyx_L3_error:;
   __Pyx_AddTraceback("cutadapt._seqio.FastqReader.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __Pyx_RefNannyFinishContext();
@@ -2831,9 +5072,9 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader___init__(CYTHON_UNUSED
   PyObject *__pyx_t_4 = NULL;
   __Pyx_RefNannySetupContext("__init__", 0);
 
-  __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_FastqReader); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 82, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_FastqReader); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 180, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_3 = PyTuple_New(2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 82, __pyx_L1_error)
+  __pyx_t_3 = PyTuple_New(2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 180, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
   __Pyx_GIVEREF(__pyx_t_2);
   PyTuple_SET_ITEM(__pyx_t_3, 0, __pyx_t_2);
@@ -2841,14 +5082,14 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader___init__(CYTHON_UNUSED
   __Pyx_GIVEREF(__pyx_v_self);
   PyTuple_SET_ITEM(__pyx_t_3, 1, __pyx_v_self);
   __pyx_t_2 = 0;
-  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_super, __pyx_t_3, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 82, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_super, __pyx_t_3, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 180, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_init); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 82, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_init); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 180, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_3);
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   __pyx_t_2 = NULL;
-  if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_3))) {
+  if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_3))) {
     __pyx_t_2 = PyMethod_GET_SELF(__pyx_t_3);
     if (likely(__pyx_t_2)) {
       PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
@@ -2858,43 +5099,25 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader___init__(CYTHON_UNUSED
     }
   }
   if (!__pyx_t_2) {
-    __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_file); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_v_file); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 180, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
   } else {
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_3)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_2, __pyx_v_file};
-      __pyx_t_1 = __Pyx_PyFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0;
-      __Pyx_GOTREF(__pyx_t_1);
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_3)) {
-      PyObject *__pyx_temp[2] = {__pyx_t_2, __pyx_v_file};
-      __pyx_t_1 = __Pyx_PyCFunction_FastCall(__pyx_t_3, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0;
-      __Pyx_GOTREF(__pyx_t_1);
-    } else
-    #endif
-    {
-      __pyx_t_4 = PyTuple_New(1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 82, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_GIVEREF(__pyx_t_2); PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_2); __pyx_t_2 = NULL;
-      __Pyx_INCREF(__pyx_v_file);
-      __Pyx_GIVEREF(__pyx_v_file);
-      PyTuple_SET_ITEM(__pyx_t_4, 0+1, __pyx_v_file);
-      __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_4, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_1);
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-    }
+    __pyx_t_4 = PyTuple_New(1+1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 180, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_4);
+    __Pyx_GIVEREF(__pyx_t_2); PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_2); __pyx_t_2 = NULL;
+    __Pyx_INCREF(__pyx_v_file);
+    __Pyx_GIVEREF(__pyx_v_file);
+    PyTuple_SET_ITEM(__pyx_t_4, 0+1, __pyx_v_file);
+    __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_3, __pyx_t_4, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 180, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_1);
+    __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
   }
   __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_sequence_class, __pyx_v_sequence_class) < 0) __PYX_ERR(0, 83, __pyx_L1_error)
+  if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_sequence_class, __pyx_v_sequence_class) < 0) __PYX_ERR(0, 181, __pyx_L1_error)
 
-  if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_delivers_qualities, Py_True) < 0) __PYX_ERR(0, 84, __pyx_L1_error)
+  if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_delivers_qualities, Py_True) < 0) __PYX_ERR(0, 182, __pyx_L1_error)
 
 
   /* function exit code */
@@ -2937,17 +5160,15 @@ static PyObject *__pyx_pf_8cutadapt_6_seqio_11FastqReader_2__iter__(CYTHON_UNUSE
   __Pyx_RefNannySetupContext("__iter__", 0);
   __pyx_cur_scope = (struct __pyx_obj_8cutadapt_6_seqio___pyx_scope_struct____iter__ *)__pyx_tp_new_8cutadapt_6_seqio___pyx_scope_struct____iter__(__pyx_ptype_8cutadapt_6_seqio___pyx_scope_struct____iter__, __pyx_empty_tuple, NULL);
   if (unlikely(!__pyx_cur_scope)) {
-    __pyx_cur_scope = ((struct __pyx_obj_8cutadapt_6_seqio___pyx_scope_struct____iter__ *)Py_None);
-    __Pyx_INCREF(Py_None);
-    __PYX_ERR(0, 86, __pyx_L1_error)
-  } else {
-    __Pyx_GOTREF(__pyx_cur_scope);
+    __Pyx_RefNannyFinishContext();
+    return NULL;
   }
+  __Pyx_GOTREF(__pyx_cur_scope);
   __pyx_cur_scope->__pyx_v_self = __pyx_v_self;
   __Pyx_INCREF(__pyx_cur_scope->__pyx_v_self);
   __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_self);
   {
-    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator, (PyObject *) __pyx_cur_scope, __pyx_n_s_iter, __pyx_n_s_FastqReader___iter, __pyx_n_s_cutadapt__seqio); if (unlikely(!gen)) __PYX_ERR(0, 86, __pyx_L1_error)
+    __pyx_CoroutineObject *gen = __Pyx_Generator_New((__pyx_coroutine_body_t) __pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator, (PyObject *) __pyx_cur_scope, __pyx_n_s_iter, __pyx_n_s_FastqReader___iter); if (unlikely(!gen)) __PYX_ERR(0, 184, __pyx_L1_error)
     __Pyx_DECREF(__pyx_cur_scope);
     __Pyx_RefNannyFinishContext();
     return (PyObject *) gen;
@@ -2976,12 +5197,12 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
   PyObject *__pyx_t_7 = NULL;
   PyObject *__pyx_t_8 = NULL;
   PyObject *__pyx_t_9 = NULL;
-  int __pyx_t_10;
+  Py_ssize_t __pyx_t_10;
   PyObject *__pyx_t_11 = NULL;
-  Py_ssize_t __pyx_t_12;
+  int __pyx_t_12;
   PyObject *(*__pyx_t_13)(PyObject *);
-  PyObject *__pyx_t_14 = NULL;
-  Py_ssize_t __pyx_t_15;
+  Py_ssize_t __pyx_t_14;
+  PyObject *__pyx_t_15 = NULL;
   Py_ssize_t __pyx_t_16;
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("None", 0);
@@ -2993,62 +5214,62 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
     return NULL;
   }
   __pyx_L3_first_run:;
-  if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 86, __pyx_L1_error)
+  if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 184, __pyx_L1_error)
 
   __pyx_cur_scope->__pyx_v_i = 0;
 
-  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_self, __pyx_n_s_sequence_class); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_self, __pyx_n_s_sequence_class); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 191, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
   __Pyx_GIVEREF(__pyx_t_1);
   __pyx_cur_scope->__pyx_v_sequence_class = __pyx_t_1;
   __pyx_t_1 = 0;
 
-  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_self, __pyx_n_s_file_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 95, __pyx_L1_error)
+  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_self, __pyx_n_s_file_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 193, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_2 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 95, __pyx_L1_error)
+  __pyx_t_2 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 193, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
   __Pyx_GIVEREF(__pyx_t_2);
   __pyx_cur_scope->__pyx_v_it = __pyx_t_2;
   __pyx_t_2 = 0;
 
-  __pyx_t_2 = __Pyx_PyIter_Next(__pyx_cur_scope->__pyx_v_it); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 96, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_PyIter_Next(__pyx_cur_scope->__pyx_v_it); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 194, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  if (!(likely(PyString_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 96, __pyx_L1_error)
+  if (!(likely(PyString_CheckExact(__pyx_t_2))||((__pyx_t_2) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_2)->tp_name), 0))) __PYX_ERR(0, 194, __pyx_L1_error)
   __Pyx_GIVEREF(__pyx_t_2);
   __pyx_cur_scope->__pyx_v_line = ((PyObject*)__pyx_t_2);
   __pyx_t_2 = 0;
 
-  __pyx_t_4 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_4 < 0)) __PYX_ERR(0, 97, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_4 < 0)) __PYX_ERR(0, 195, __pyx_L1_error)
   if (__pyx_t_4) {
   } else {
     __pyx_t_3 = __pyx_t_4;
     goto __pyx_L5_bool_binop_done;
   }
-  __pyx_t_2 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 97, __pyx_L1_error)
+  __pyx_t_2 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 195, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
-  __pyx_t_4 = (__Pyx_PyString_Equals(__pyx_t_2, __pyx_kp_s__2, Py_EQ)); if (unlikely(__pyx_t_4 < 0)) __PYX_ERR(0, 97, __pyx_L1_error)
+  __pyx_t_4 = (__Pyx_PyString_Equals(__pyx_t_2, __pyx_kp_s__17, Py_EQ)); if (unlikely(__pyx_t_4 < 0)) __PYX_ERR(0, 195, __pyx_L1_error)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   __pyx_t_3 = __pyx_t_4;
   __pyx_L5_bool_binop_done:;
   __pyx_t_4 = ((!__pyx_t_3) != 0);
   if (__pyx_t_4) {
 
-    __pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 98, __pyx_L1_error)
+    __pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 196, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_1);
-    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 98, __pyx_L1_error)
+    __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 196, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_6);
-    __pyx_t_7 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 98, __pyx_L1_error)
+    __pyx_t_7 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 196, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_7);
     if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
       PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-      __PYX_ERR(0, 98, __pyx_L1_error)
+      __PYX_ERR(0, 196, __pyx_L1_error)
     }
-    __pyx_t_8 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 98, __pyx_L1_error)
+    __pyx_t_8 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 196, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
     __pyx_t_9 = NULL;
     __pyx_t_10 = 0;
-    if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_6))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_6))) {
       __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_6);
       if (likely(__pyx_t_9)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6);
@@ -3058,45 +5279,23 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
         __pyx_t_10 = 1;
       }
     }
-    #if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(__pyx_t_6)) {
-      PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_t_7, __pyx_t_8};
-      __pyx_t_5 = __Pyx_PyFunction_FastCall(__pyx_t_6, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-      __Pyx_GOTREF(__pyx_t_5);
-      __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    } else
-    #endif
-    #if CYTHON_FAST_PYCCALL
-    if (__Pyx_PyFastCFunction_Check(__pyx_t_6)) {
-      PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_t_7, __pyx_t_8};
-      __pyx_t_5 = __Pyx_PyCFunction_FastCall(__pyx_t_6, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
-      __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-      __Pyx_GOTREF(__pyx_t_5);
-      __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    } else
-    #endif
-    {
-      __pyx_t_11 = PyTuple_New(2+__pyx_t_10); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 98, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_11);
-      if (__pyx_t_9) {
-        __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_11, 0, __pyx_t_9); __pyx_t_9 = NULL;
-      }
-      __Pyx_GIVEREF(__pyx_t_7);
-      PyTuple_SET_ITEM(__pyx_t_11, 0+__pyx_t_10, __pyx_t_7);
-      __Pyx_GIVEREF(__pyx_t_8);
-      PyTuple_SET_ITEM(__pyx_t_11, 1+__pyx_t_10, __pyx_t_8);
-      __pyx_t_7 = 0;
-      __pyx_t_8 = 0;
-      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_11, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 98, __pyx_L1_error)
-      __Pyx_GOTREF(__pyx_t_5);
-      __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
+    __pyx_t_11 = PyTuple_New(2+__pyx_t_10); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 196, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_11);
+    if (__pyx_t_9) {
+      __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_11, 0, __pyx_t_9); __pyx_t_9 = NULL;
     }
+    __Pyx_GIVEREF(__pyx_t_7);
+    PyTuple_SET_ITEM(__pyx_t_11, 0+__pyx_t_10, __pyx_t_7);
+    __Pyx_GIVEREF(__pyx_t_8);
+    PyTuple_SET_ITEM(__pyx_t_11, 1+__pyx_t_10, __pyx_t_8);
+    __pyx_t_7 = 0;
+    __pyx_t_8 = 0;
+    __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_11, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 196, __pyx_L1_error)
+    __Pyx_GOTREF(__pyx_t_5);
+    __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
     __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
     __pyx_t_6 = NULL;
-    if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_1))) {
+    if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_1))) {
       __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_1);
       if (likely(__pyx_t_6)) {
         PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_1);
@@ -3106,64 +5305,44 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       }
     }
     if (!__pyx_t_6) {
-      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_1, __pyx_t_5); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error)
+      __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_1, __pyx_t_5); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 196, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
       __Pyx_GOTREF(__pyx_t_2);
     } else {
-      #if CYTHON_FAST_PYCALL
-      if (PyFunction_Check(__pyx_t_1)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-        __pyx_t_2 = __Pyx_PyFunction_FastCall(__pyx_t_1, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      } else
-      #endif
-      #if CYTHON_FAST_PYCCALL
-      if (__Pyx_PyFastCFunction_Check(__pyx_t_1)) {
-        PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-        __pyx_t_2 = __Pyx_PyCFunction_FastCall(__pyx_t_1, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      } else
-      #endif
-      {
-        __pyx_t_11 = PyTuple_New(1+1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_11);
-        __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_11, 0, __pyx_t_6); __pyx_t_6 = NULL;
-        __Pyx_GIVEREF(__pyx_t_5);
-        PyTuple_SET_ITEM(__pyx_t_11, 0+1, __pyx_t_5);
-        __pyx_t_5 = 0;
-        __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_11, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 98, __pyx_L1_error)
-        __Pyx_GOTREF(__pyx_t_2);
-        __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
-      }
+      __pyx_t_11 = PyTuple_New(1+1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 196, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_11);
+      __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_11, 0, __pyx_t_6); __pyx_t_6 = NULL;
+      __Pyx_GIVEREF(__pyx_t_5);
+      PyTuple_SET_ITEM(__pyx_t_11, 0+1, __pyx_t_5);
+      __pyx_t_5 = 0;
+      __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_11, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 196, __pyx_L1_error)
+      __Pyx_GOTREF(__pyx_t_2);
+      __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
     }
     __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
     __Pyx_Raise(__pyx_t_2, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
-    __PYX_ERR(0, 98, __pyx_L1_error)
+    __PYX_ERR(0, 196, __pyx_L1_error)
 
   }
 
   if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
     PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%s'", "endswith");
-    __PYX_ERR(0, 99, __pyx_L1_error)
+    __PYX_ERR(0, 197, __pyx_L1_error)
   }
-  __pyx_t_4 = __Pyx_PyStr_Tailmatch(__pyx_cur_scope->__pyx_v_line, __pyx_kp_s__3, 0, PY_SSIZE_T_MAX, 1); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 99, __pyx_L1_error)
+  __pyx_t_4 = __Pyx_PyStr_Tailmatch(__pyx_cur_scope->__pyx_v_line, __pyx_kp_s__18, 0, PY_SSIZE_T_MAX, 1); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 197, __pyx_L1_error)
   if ((__pyx_t_4 != 0)) {
-    __pyx_t_10 = -2;
+    __pyx_t_12 = -2;
   } else {
-    __pyx_t_10 = -1;
+    __pyx_t_12 = -1;
   }
-  __pyx_cur_scope->__pyx_v_strip = __pyx_t_10;
+  __pyx_cur_scope->__pyx_v_strip = __pyx_t_12;
 
   if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
     PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-    __PYX_ERR(0, 100, __pyx_L1_error)
+    __PYX_ERR(0, 198, __pyx_L1_error)
   }
-  __pyx_t_2 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 100, __pyx_L1_error)
+  __pyx_t_2 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 198, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
   __Pyx_GIVEREF(__pyx_t_2);
   __pyx_cur_scope->__pyx_v_name = ((PyObject*)__pyx_t_2);
@@ -3172,29 +5351,29 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
   __pyx_cur_scope->__pyx_v_i = 1;
 
   if (likely(PyList_CheckExact(__pyx_cur_scope->__pyx_v_it)) || PyTuple_CheckExact(__pyx_cur_scope->__pyx_v_it)) {
-    __pyx_t_2 = __pyx_cur_scope->__pyx_v_it; __Pyx_INCREF(__pyx_t_2); __pyx_t_12 = 0;
+    __pyx_t_2 = __pyx_cur_scope->__pyx_v_it; __Pyx_INCREF(__pyx_t_2); __pyx_t_10 = 0;
     __pyx_t_13 = NULL;
   } else {
-    __pyx_t_12 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_cur_scope->__pyx_v_it); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 103, __pyx_L1_error)
+    __pyx_t_10 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_cur_scope->__pyx_v_it); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 201, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
-    __pyx_t_13 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 103, __pyx_L1_error)
+    __pyx_t_13 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 201, __pyx_L1_error)
   }
   for (;;) {
     if (likely(!__pyx_t_13)) {
       if (likely(PyList_CheckExact(__pyx_t_2))) {
-        if (__pyx_t_12 >= PyList_GET_SIZE(__pyx_t_2)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_1 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_12); __Pyx_INCREF(__pyx_t_1); __pyx_t_12++; if (unlikely(0 < 0)) __PYX_ERR(0, 103, __pyx_L1_error)
+        if (__pyx_t_10 >= PyList_GET_SIZE(__pyx_t_2)) break;
+        #if CYTHON_COMPILING_IN_CPYTHON
+        __pyx_t_1 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_10); __Pyx_INCREF(__pyx_t_1); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 201, __pyx_L1_error)
         #else
-        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_12); __pyx_t_12++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 103, __pyx_L1_error)
+        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 201, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         #endif
       } else {
-        if (__pyx_t_12 >= PyTuple_GET_SIZE(__pyx_t_2)) break;
-        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-        __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_12); __Pyx_INCREF(__pyx_t_1); __pyx_t_12++; if (unlikely(0 < 0)) __PYX_ERR(0, 103, __pyx_L1_error)
+        if (__pyx_t_10 >= PyTuple_GET_SIZE(__pyx_t_2)) break;
+        #if CYTHON_COMPILING_IN_CPYTHON
+        __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_10); __Pyx_INCREF(__pyx_t_1); __pyx_t_10++; if (unlikely(0 < 0)) __PYX_ERR(0, 201, __pyx_L1_error)
         #else
-        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_12); __pyx_t_12++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 103, __pyx_L1_error)
+        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_10); __pyx_t_10++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 201, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         #endif
       }
@@ -3204,13 +5383,13 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
         PyObject* exc_type = PyErr_Occurred();
         if (exc_type) {
           if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
-          else __PYX_ERR(0, 103, __pyx_L1_error)
+          else __PYX_ERR(0, 201, __pyx_L1_error)
         }
         break;
       }
       __Pyx_GOTREF(__pyx_t_1);
     }
-    if (!(likely(PyString_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(0, 103, __pyx_L1_error)
+    if (!(likely(PyString_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(0, 201, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_cur_scope->__pyx_v_line);
     __Pyx_DECREF_SET(__pyx_cur_scope->__pyx_v_line, ((PyObject*)__pyx_t_1));
     __Pyx_GIVEREF(__pyx_t_1);
@@ -3219,84 +5398,62 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
     switch (__pyx_cur_scope->__pyx_v_i) {
       case 0:
 
-      __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 105, __pyx_L1_error)
+      __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 203, __pyx_L1_error)
       if (__pyx_t_3) {
       } else {
         __pyx_t_4 = __pyx_t_3;
         goto __pyx_L10_bool_binop_done;
       }
-      __pyx_t_1 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 105, __pyx_L1_error)
+      __pyx_t_1 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 203, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_kp_s__2, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 105, __pyx_L1_error)
+      __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_kp_s__17, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 203, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
       __pyx_t_4 = __pyx_t_3;
       __pyx_L10_bool_binop_done:;
       __pyx_t_3 = ((!__pyx_t_4) != 0);
       if (__pyx_t_3) {
 
-        __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 106, __pyx_L1_error)
+        __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 204, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_11);
-        __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 106, __pyx_L1_error)
+        __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected, __pyx_n_s_format); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 204, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_6);
-        __pyx_t_8 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 106, __pyx_L1_error)
+        __pyx_t_8 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 204, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_8);
         if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
           PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-          __PYX_ERR(0, 106, __pyx_L1_error)
+          __PYX_ERR(0, 204, __pyx_L1_error)
         }
-        __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 106, __pyx_L1_error)
+        __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 204, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_7);
         __pyx_t_9 = NULL;
-        __pyx_t_10 = 0;
-        if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_6))) {
+        __pyx_t_14 = 0;
+        if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_6))) {
           __pyx_t_9 = PyMethod_GET_SELF(__pyx_t_6);
           if (likely(__pyx_t_9)) {
             PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_6);
             __Pyx_INCREF(__pyx_t_9);
             __Pyx_INCREF(function);
             __Pyx_DECREF_SET(__pyx_t_6, function);
-            __pyx_t_10 = 1;
+            __pyx_t_14 = 1;
           }
         }
-        #if CYTHON_FAST_PYCALL
-        if (PyFunction_Check(__pyx_t_6)) {
-          PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_t_8, __pyx_t_7};
-          __pyx_t_5 = __Pyx_PyFunction_FastCall(__pyx_t_6, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 106, __pyx_L1_error)
-          __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-          __Pyx_GOTREF(__pyx_t_5);
-          __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-          __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-        } else
-        #endif
-        #if CYTHON_FAST_PYCCALL
-        if (__Pyx_PyFastCFunction_Check(__pyx_t_6)) {
-          PyObject *__pyx_temp[3] = {__pyx_t_9, __pyx_t_8, __pyx_t_7};
-          __pyx_t_5 = __Pyx_PyCFunction_FastCall(__pyx_t_6, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 106, __pyx_L1_error)
-          __Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
-          __Pyx_GOTREF(__pyx_t_5);
-          __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-          __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-        } else
-        #endif
-        {
-          __pyx_t_14 = PyTuple_New(2+__pyx_t_10); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 106, __pyx_L1_error)
-          __Pyx_GOTREF(__pyx_t_14);
-          if (__pyx_t_9) {
-            __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_t_9); __pyx_t_9 = NULL;
-          }
-          __Pyx_GIVEREF(__pyx_t_8);
-          PyTuple_SET_ITEM(__pyx_t_14, 0+__pyx_t_10, __pyx_t_8);
-          __Pyx_GIVEREF(__pyx_t_7);
-          PyTuple_SET_ITEM(__pyx_t_14, 1+__pyx_t_10, __pyx_t_7);
-          __pyx_t_8 = 0;
-          __pyx_t_7 = 0;
-          __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_14, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 106, __pyx_L1_error)
-          __Pyx_GOTREF(__pyx_t_5);
-          __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+        __pyx_t_15 = PyTuple_New(2+__pyx_t_14); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 204, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_15);
+        if (__pyx_t_9) {
+          __Pyx_GIVEREF(__pyx_t_9); PyTuple_SET_ITEM(__pyx_t_15, 0, __pyx_t_9); __pyx_t_9 = NULL;
         }
+        __Pyx_GIVEREF(__pyx_t_8);
+        PyTuple_SET_ITEM(__pyx_t_15, 0+__pyx_t_14, __pyx_t_8);
+        __Pyx_GIVEREF(__pyx_t_7);
+        PyTuple_SET_ITEM(__pyx_t_15, 1+__pyx_t_14, __pyx_t_7);
+        __pyx_t_8 = 0;
+        __pyx_t_7 = 0;
+        __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_15, NULL); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 204, __pyx_L1_error)
+        __Pyx_GOTREF(__pyx_t_5);
+        __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0;
         __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
         __pyx_t_6 = NULL;
-        if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_11))) {
+        if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_11))) {
           __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_11);
           if (likely(__pyx_t_6)) {
             PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_11);
@@ -3306,52 +5463,32 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
           }
         }
         if (!__pyx_t_6) {
-          __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
+          __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_5); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 204, __pyx_L1_error)
           __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
           __Pyx_GOTREF(__pyx_t_1);
         } else {
-          #if CYTHON_FAST_PYCALL
-          if (PyFunction_Check(__pyx_t_11)) {
-            PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-            __pyx_t_1 = __Pyx_PyFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
-            __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-            __Pyx_GOTREF(__pyx_t_1);
-            __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-          } else
-          #endif
-          #if CYTHON_FAST_PYCCALL
-          if (__Pyx_PyFastCFunction_Check(__pyx_t_11)) {
-            PyObject *__pyx_temp[2] = {__pyx_t_6, __pyx_t_5};
-            __pyx_t_1 = __Pyx_PyCFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
-            __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-            __Pyx_GOTREF(__pyx_t_1);
-            __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-          } else
-          #endif
-          {
-            __pyx_t_14 = PyTuple_New(1+1); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 106, __pyx_L1_error)
-            __Pyx_GOTREF(__pyx_t_14);
-            __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_14, 0, __pyx_t_6); __pyx_t_6 = NULL;
-            __Pyx_GIVEREF(__pyx_t_5);
-            PyTuple_SET_ITEM(__pyx_t_14, 0+1, __pyx_t_5);
-            __pyx_t_5 = 0;
-            __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_14, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 106, __pyx_L1_error)
-            __Pyx_GOTREF(__pyx_t_1);
-            __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
-          }
+          __pyx_t_15 = PyTuple_New(1+1); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 204, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_15);
+          __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_15, 0, __pyx_t_6); __pyx_t_6 = NULL;
+          __Pyx_GIVEREF(__pyx_t_5);
+          PyTuple_SET_ITEM(__pyx_t_15, 0+1, __pyx_t_5);
+          __pyx_t_5 = 0;
+          __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_15, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 204, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_1);
+          __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0;
         }
         __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
         __Pyx_Raise(__pyx_t_1, 0, 0, 0);
         __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-        __PYX_ERR(0, 106, __pyx_L1_error)
+        __PYX_ERR(0, 204, __pyx_L1_error)
 
       }
 
       if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
         PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-        __PYX_ERR(0, 107, __pyx_L1_error)
+        __PYX_ERR(0, 205, __pyx_L1_error)
       }
-      __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 107, __pyx_L1_error)
+      __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 205, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
       __Pyx_GOTREF(__pyx_cur_scope->__pyx_v_name);
       __Pyx_DECREF_SET(__pyx_cur_scope->__pyx_v_name, ((PyObject*)__pyx_t_1));
@@ -3364,9 +5501,9 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
 
       if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
         PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-        __PYX_ERR(0, 109, __pyx_L1_error)
+        __PYX_ERR(0, 207, __pyx_L1_error)
       }
-      __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 109, __pyx_L1_error)
+      __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 207, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
       __Pyx_XGOTREF(__pyx_cur_scope->__pyx_v_sequence);
       __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_v_sequence, ((PyObject*)__pyx_t_1));
@@ -3377,14 +5514,14 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
 
       case 2:
 
-      __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_cur_scope->__pyx_v_line, __pyx_kp_s__4, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 111, __pyx_L1_error)
+      __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_cur_scope->__pyx_v_line, __pyx_kp_s__19, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 209, __pyx_L1_error)
       __pyx_t_4 = (__pyx_t_3 != 0);
       if (__pyx_t_4) {
 
-        __Pyx_INCREF(__pyx_kp_s_);
+        __Pyx_INCREF(__pyx_kp_s__16);
         __Pyx_XGOTREF(__pyx_cur_scope->__pyx_v_name2);
-        __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_v_name2, __pyx_kp_s_);
-        __Pyx_GIVEREF(__pyx_kp_s_);
+        __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_v_name2, __pyx_kp_s__16);
+        __Pyx_GIVEREF(__pyx_kp_s__16);
 
         goto __pyx_L12;
       }
@@ -3392,89 +5529,67 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       /*else*/ {
         if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
           PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-          __PYX_ERR(0, 114, __pyx_L1_error)
+          __PYX_ERR(0, 212, __pyx_L1_error)
         }
-        __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 114, __pyx_L1_error)
+        __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 212, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         __Pyx_GOTREF(__pyx_cur_scope->__pyx_v_line);
         __Pyx_DECREF_SET(__pyx_cur_scope->__pyx_v_line, ((PyObject*)__pyx_t_1));
         __Pyx_GIVEREF(__pyx_t_1);
         __pyx_t_1 = 0;
 
-        __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 115, __pyx_L1_error)
+        __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 213, __pyx_L1_error)
         if (__pyx_t_3) {
         } else {
           __pyx_t_4 = __pyx_t_3;
           goto __pyx_L14_bool_binop_done;
         }
-        __pyx_t_1 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 115, __pyx_L1_error)
+        __pyx_t_1 = __Pyx_GetItemInt(__pyx_cur_scope->__pyx_v_line, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 213, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
-        __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_kp_s__5, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 115, __pyx_L1_error)
+        __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_kp_s__20, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 213, __pyx_L1_error)
         __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
         __pyx_t_4 = __pyx_t_3;
         __pyx_L14_bool_binop_done:;
         __pyx_t_3 = ((!__pyx_t_4) != 0);
         if (__pyx_t_3) {
 
-          __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 116, __pyx_L1_error)
+          __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 214, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_11);
-          __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected_2, __pyx_n_s_format); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 116, __pyx_L1_error)
+          __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_Line_0_in_FASTQ_file_is_expected_2, __pyx_n_s_format); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 214, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_5);
-          __pyx_t_6 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 116, __pyx_L1_error)
+          __pyx_t_6 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 214, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_6);
-          __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 116, __pyx_L1_error)
+          __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, 10); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 214, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_7);
           __pyx_t_8 = NULL;
-          __pyx_t_10 = 0;
-          if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_5))) {
+          __pyx_t_14 = 0;
+          if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_5))) {
             __pyx_t_8 = PyMethod_GET_SELF(__pyx_t_5);
             if (likely(__pyx_t_8)) {
               PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_5);
               __Pyx_INCREF(__pyx_t_8);
               __Pyx_INCREF(function);
               __Pyx_DECREF_SET(__pyx_t_5, function);
-              __pyx_t_10 = 1;
+              __pyx_t_14 = 1;
             }
           }
-          #if CYTHON_FAST_PYCALL
-          if (PyFunction_Check(__pyx_t_5)) {
-            PyObject *__pyx_temp[3] = {__pyx_t_8, __pyx_t_6, __pyx_t_7};
-            __pyx_t_14 = __Pyx_PyFunction_FastCall(__pyx_t_5, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 116, __pyx_L1_error)
-            __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-            __Pyx_GOTREF(__pyx_t_14);
-            __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-            __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-          } else
-          #endif
-          #if CYTHON_FAST_PYCCALL
-          if (__Pyx_PyFastCFunction_Check(__pyx_t_5)) {
-            PyObject *__pyx_temp[3] = {__pyx_t_8, __pyx_t_6, __pyx_t_7};
-            __pyx_t_14 = __Pyx_PyCFunction_FastCall(__pyx_t_5, __pyx_temp+1-__pyx_t_10, 2+__pyx_t_10); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 116, __pyx_L1_error)
-            __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
-            __Pyx_GOTREF(__pyx_t_14);
-            __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-            __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-          } else
-          #endif
-          {
-            __pyx_t_9 = PyTuple_New(2+__pyx_t_10); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 116, __pyx_L1_error)
-            __Pyx_GOTREF(__pyx_t_9);
-            if (__pyx_t_8) {
-              __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
-            }
-            __Pyx_GIVEREF(__pyx_t_6);
-            PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_10, __pyx_t_6);
-            __Pyx_GIVEREF(__pyx_t_7);
-            PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_10, __pyx_t_7);
-            __pyx_t_6 = 0;
-            __pyx_t_7 = 0;
-            __pyx_t_14 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_9, NULL); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 116, __pyx_L1_error)
-            __Pyx_GOTREF(__pyx_t_14);
-            __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+          __pyx_t_9 = PyTuple_New(2+__pyx_t_14); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 214, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_9);
+          if (__pyx_t_8) {
+            __Pyx_GIVEREF(__pyx_t_8); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8); __pyx_t_8 = NULL;
           }
+          __Pyx_GIVEREF(__pyx_t_6);
+          PyTuple_SET_ITEM(__pyx_t_9, 0+__pyx_t_14, __pyx_t_6);
+          __Pyx_GIVEREF(__pyx_t_7);
+          PyTuple_SET_ITEM(__pyx_t_9, 1+__pyx_t_14, __pyx_t_7);
+          __pyx_t_6 = 0;
+          __pyx_t_7 = 0;
+          __pyx_t_15 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_9, NULL); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 214, __pyx_L1_error)
+          __Pyx_GOTREF(__pyx_t_15);
+          __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
           __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
           __pyx_t_5 = NULL;
-          if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_11))) {
+          if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_11))) {
             __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_11);
             if (likely(__pyx_t_5)) {
               PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_11);
@@ -3484,169 +5599,107 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
             }
           }
           if (!__pyx_t_5) {
-            __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_14); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 116, __pyx_L1_error)
-            __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
+            __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_15); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 214, __pyx_L1_error)
+            __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0;
             __Pyx_GOTREF(__pyx_t_1);
           } else {
-            #if CYTHON_FAST_PYCALL
-            if (PyFunction_Check(__pyx_t_11)) {
-              PyObject *__pyx_temp[2] = {__pyx_t_5, __pyx_t_14};
-              __pyx_t_1 = __Pyx_PyFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 116, __pyx_L1_error)
-              __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-              __Pyx_GOTREF(__pyx_t_1);
-              __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
-            } else
-            #endif
-            #if CYTHON_FAST_PYCCALL
-            if (__Pyx_PyFastCFunction_Check(__pyx_t_11)) {
-              PyObject *__pyx_temp[2] = {__pyx_t_5, __pyx_t_14};
-              __pyx_t_1 = __Pyx_PyCFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 116, __pyx_L1_error)
-              __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
-              __Pyx_GOTREF(__pyx_t_1);
-              __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
-            } else
-            #endif
-            {
-              __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 116, __pyx_L1_error)
-              __Pyx_GOTREF(__pyx_t_9);
-              __Pyx_GIVEREF(__pyx_t_5); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_5); __pyx_t_5 = NULL;
-              __Pyx_GIVEREF(__pyx_t_14);
-              PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_t_14);
-              __pyx_t_14 = 0;
-              __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_9, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 116, __pyx_L1_error)
-              __Pyx_GOTREF(__pyx_t_1);
-              __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-            }
+            __pyx_t_9 = PyTuple_New(1+1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 214, __pyx_L1_error)
+            __Pyx_GOTREF(__pyx_t_9);
+            __Pyx_GIVEREF(__pyx_t_5); PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_5); __pyx_t_5 = NULL;
+            __Pyx_GIVEREF(__pyx_t_15);
+            PyTuple_SET_ITEM(__pyx_t_9, 0+1, __pyx_t_15);
+            __pyx_t_15 = 0;
+            __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_9, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 214, __pyx_L1_error)
+            __Pyx_GOTREF(__pyx_t_1);
+            __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
           }
           __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
           __Pyx_Raise(__pyx_t_1, 0, 0, 0);
           __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-          __PYX_ERR(0, 116, __pyx_L1_error)
+          __PYX_ERR(0, 214, __pyx_L1_error)
 
         }
 
-        __pyx_t_15 = PyObject_Length(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_15 == -1)) __PYX_ERR(0, 117, __pyx_L1_error)
-        __pyx_t_3 = ((__pyx_t_15 > 1) != 0);
+        __pyx_t_14 = PyObject_Length(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_14 == -1)) __PYX_ERR(0, 215, __pyx_L1_error)
+        __pyx_t_3 = ((__pyx_t_14 > 1) != 0);
         if (__pyx_t_3) {
 
-          __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, PY_SSIZE_T_MAX); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 118, __pyx_L1_error)
+          __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, PY_SSIZE_T_MAX); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 216, __pyx_L1_error)
           __Pyx_GOTREF(__pyx_t_1);
-          __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_cur_scope->__pyx_v_name, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 118, __pyx_L1_error)
+          __pyx_t_3 = (__Pyx_PyString_Equals(__pyx_t_1, __pyx_cur_scope->__pyx_v_name, Py_EQ)); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 216, __pyx_L1_error)
           __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
           __pyx_t_4 = ((!(__pyx_t_3 != 0)) != 0);
           if (__pyx_t_4) {
 
-            __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 119, __pyx_L1_error)
+            __pyx_t_11 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 217, __pyx_L1_error)
             __Pyx_GOTREF(__pyx_t_11);
 
-            __pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_At_line_0_Sequence_descriptions, __pyx_n_s_format); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 123, __pyx_L1_error)
-            __Pyx_GOTREF(__pyx_t_14);
-            __pyx_t_5 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 123, __pyx_L1_error)
+            __pyx_t_15 = __Pyx_PyObject_GetAttrStr(__pyx_kp_s_At_line_0_Sequence_descriptions, __pyx_n_s_format); if (unlikely(!__pyx_t_15)) __PYX_ERR(0, 221, __pyx_L1_error)
+            __Pyx_GOTREF(__pyx_t_15);
+            __pyx_t_5 = __Pyx_PyInt_From_long((__pyx_cur_scope->__pyx_v_i + 1)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 221, __pyx_L1_error)
             __Pyx_GOTREF(__pyx_t_5);
 
-            __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, PY_SSIZE_T_MAX); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 124, __pyx_L1_error)
+            __pyx_t_7 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 1, PY_SSIZE_T_MAX); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 222, __pyx_L1_error)
             __Pyx_GOTREF(__pyx_t_7);
             __pyx_t_6 = NULL;
-            __pyx_t_10 = 0;
-            if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_14))) {
-              __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_14);
+            __pyx_t_14 = 0;
+            if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_15))) {
+              __pyx_t_6 = PyMethod_GET_SELF(__pyx_t_15);
               if (likely(__pyx_t_6)) {
-                PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_14);
+                PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_15);
                 __Pyx_INCREF(__pyx_t_6);
                 __Pyx_INCREF(function);
-                __Pyx_DECREF_SET(__pyx_t_14, function);
-                __pyx_t_10 = 1;
+                __Pyx_DECREF_SET(__pyx_t_15, function);
+                __pyx_t_14 = 1;
               }
             }
-            #if CYTHON_FAST_PYCALL
-            if (PyFunction_Check(__pyx_t_14)) {
-              PyObject *__pyx_temp[4] = {__pyx_t_6, __pyx_t_5, __pyx_cur_scope->__pyx_v_name, __pyx_t_7};
-              __pyx_t_9 = __Pyx_PyFunction_FastCall(__pyx_t_14, __pyx_temp+1-__pyx_t_10, 3+__pyx_t_10); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 123, __pyx_L1_error)
-              __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-              __Pyx_GOTREF(__pyx_t_9);
-              __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-              __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-            } else
-            #endif
-            #if CYTHON_FAST_PYCCALL
-            if (__Pyx_PyFastCFunction_Check(__pyx_t_14)) {
-              PyObject *__pyx_temp[4] = {__pyx_t_6, __pyx_t_5, __pyx_cur_scope->__pyx_v_name, __pyx_t_7};
-              __pyx_t_9 = __Pyx_PyCFunction_FastCall(__pyx_t_14, __pyx_temp+1-__pyx_t_10, 3+__pyx_t_10); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 123, __pyx_L1_error)
-              __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
-              __Pyx_GOTREF(__pyx_t_9);
-              __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-              __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
-            } else
-            #endif
-            {
-              __pyx_t_8 = PyTuple_New(3+__pyx_t_10); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 123, __pyx_L1_error)
-              __Pyx_GOTREF(__pyx_t_8);
-              if (__pyx_t_6) {
-                __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_6); __pyx_t_6 = NULL;
-              }
-              __Pyx_GIVEREF(__pyx_t_5);
-              PyTuple_SET_ITEM(__pyx_t_8, 0+__pyx_t_10, __pyx_t_5);
-              __Pyx_INCREF(__pyx_cur_scope->__pyx_v_name);
-              __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_name);
-              PyTuple_SET_ITEM(__pyx_t_8, 1+__pyx_t_10, __pyx_cur_scope->__pyx_v_name);
-              __Pyx_GIVEREF(__pyx_t_7);
-              PyTuple_SET_ITEM(__pyx_t_8, 2+__pyx_t_10, __pyx_t_7);
-              __pyx_t_5 = 0;
-              __pyx_t_7 = 0;
-              __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_14, __pyx_t_8, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 123, __pyx_L1_error)
-              __Pyx_GOTREF(__pyx_t_9);
-              __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+            __pyx_t_8 = PyTuple_New(3+__pyx_t_14); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 221, __pyx_L1_error)
+            __Pyx_GOTREF(__pyx_t_8);
+            if (__pyx_t_6) {
+              __Pyx_GIVEREF(__pyx_t_6); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_6); __pyx_t_6 = NULL;
             }
-            __Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
-            __pyx_t_14 = NULL;
-            if (CYTHON_UNPACK_METHODS && unlikely(PyMethod_Check(__pyx_t_11))) {
-              __pyx_t_14 = PyMethod_GET_SELF(__pyx_t_11);
-              if (likely(__pyx_t_14)) {
+            __Pyx_GIVEREF(__pyx_t_5);
+            PyTuple_SET_ITEM(__pyx_t_8, 0+__pyx_t_14, __pyx_t_5);
+            __Pyx_INCREF(__pyx_cur_scope->__pyx_v_name);
+            __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_name);
+            PyTuple_SET_ITEM(__pyx_t_8, 1+__pyx_t_14, __pyx_cur_scope->__pyx_v_name);
+            __Pyx_GIVEREF(__pyx_t_7);
+            PyTuple_SET_ITEM(__pyx_t_8, 2+__pyx_t_14, __pyx_t_7);
+            __pyx_t_5 = 0;
+            __pyx_t_7 = 0;
+            __pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_15, __pyx_t_8, NULL); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 221, __pyx_L1_error)
+            __Pyx_GOTREF(__pyx_t_9);
+            __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+            __Pyx_DECREF(__pyx_t_15); __pyx_t_15 = 0;
+            __pyx_t_15 = NULL;
+            if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_11))) {
+              __pyx_t_15 = PyMethod_GET_SELF(__pyx_t_11);
+              if (likely(__pyx_t_15)) {
                 PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_11);
-                __Pyx_INCREF(__pyx_t_14);
+                __Pyx_INCREF(__pyx_t_15);
                 __Pyx_INCREF(function);
                 __Pyx_DECREF_SET(__pyx_t_11, function);
               }
             }
-            if (!__pyx_t_14) {
-              __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_9); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 119, __pyx_L1_error)
+            if (!__pyx_t_15) {
+              __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_11, __pyx_t_9); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 217, __pyx_L1_error)
               __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
               __Pyx_GOTREF(__pyx_t_1);
             } else {
-              #if CYTHON_FAST_PYCALL
-              if (PyFunction_Check(__pyx_t_11)) {
-                PyObject *__pyx_temp[2] = {__pyx_t_14, __pyx_t_9};
-                __pyx_t_1 = __Pyx_PyFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 119, __pyx_L1_error)
-                __Pyx_XDECREF(__pyx_t_14); __pyx_t_14 = 0;
-                __Pyx_GOTREF(__pyx_t_1);
-                __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-              } else
-              #endif
-              #if CYTHON_FAST_PYCCALL
-              if (__Pyx_PyFastCFunction_Check(__pyx_t_11)) {
-                PyObject *__pyx_temp[2] = {__pyx_t_14, __pyx_t_9};
-                __pyx_t_1 = __Pyx_PyCFunction_FastCall(__pyx_t_11, __pyx_temp+1-1, 1+1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 119, __pyx_L1_error)
-                __Pyx_XDECREF(__pyx_t_14); __pyx_t_14 = 0;
-                __Pyx_GOTREF(__pyx_t_1);
-                __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
-              } else
-              #endif
-              {
-                __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 119, __pyx_L1_error)
-                __Pyx_GOTREF(__pyx_t_8);
-                __Pyx_GIVEREF(__pyx_t_14); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_14); __pyx_t_14 = NULL;
-                __Pyx_GIVEREF(__pyx_t_9);
-                PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_t_9);
-                __pyx_t_9 = 0;
-                __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_8, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 119, __pyx_L1_error)
-                __Pyx_GOTREF(__pyx_t_1);
-                __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-              }
+              __pyx_t_8 = PyTuple_New(1+1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 217, __pyx_L1_error)
+              __Pyx_GOTREF(__pyx_t_8);
+              __Pyx_GIVEREF(__pyx_t_15); PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_15); __pyx_t_15 = NULL;
+              __Pyx_GIVEREF(__pyx_t_9);
+              PyTuple_SET_ITEM(__pyx_t_8, 0+1, __pyx_t_9);
+              __pyx_t_9 = 0;
+              __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_11, __pyx_t_8, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 217, __pyx_L1_error)
+              __Pyx_GOTREF(__pyx_t_1);
+              __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
             }
             __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
             __Pyx_Raise(__pyx_t_1, 0, 0, 0);
             __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-            __PYX_ERR(0, 119, __pyx_L1_error)
+            __PYX_ERR(0, 217, __pyx_L1_error)
 
           }
 
@@ -3666,17 +5719,17 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
 
       case 3:
 
-      __pyx_t_15 = PyObject_Length(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_15 == -1)) __PYX_ERR(0, 129, __pyx_L1_error)
-      if (unlikely(!__pyx_cur_scope->__pyx_v_sequence)) { __Pyx_RaiseUnboundLocalError("sequence"); __PYX_ERR(0, 129, __pyx_L1_error) }
-      __pyx_t_16 = PyObject_Length(__pyx_cur_scope->__pyx_v_sequence); if (unlikely(__pyx_t_16 == -1)) __PYX_ERR(0, 129, __pyx_L1_error)
-      __pyx_t_4 = ((__pyx_t_15 == (__pyx_t_16 - __pyx_cur_scope->__pyx_v_strip)) != 0);
+      __pyx_t_14 = PyObject_Length(__pyx_cur_scope->__pyx_v_line); if (unlikely(__pyx_t_14 == -1)) __PYX_ERR(0, 227, __pyx_L1_error)
+      if (unlikely(!__pyx_cur_scope->__pyx_v_sequence)) { __Pyx_RaiseUnboundLocalError("sequence"); __PYX_ERR(0, 227, __pyx_L1_error) }
+      __pyx_t_16 = PyObject_Length(__pyx_cur_scope->__pyx_v_sequence); if (unlikely(__pyx_t_16 == -1)) __PYX_ERR(0, 227, __pyx_L1_error)
+      __pyx_t_4 = ((__pyx_t_14 == (__pyx_t_16 - __pyx_cur_scope->__pyx_v_strip)) != 0);
       if (__pyx_t_4) {
 
         if (unlikely(__pyx_cur_scope->__pyx_v_line == Py_None)) {
           PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable");
-          __PYX_ERR(0, 130, __pyx_L1_error)
+          __PYX_ERR(0, 228, __pyx_L1_error)
         }
-        __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 130, __pyx_L1_error)
+        __pyx_t_1 = PySequence_GetSlice(__pyx_cur_scope->__pyx_v_line, 0, __pyx_cur_scope->__pyx_v_strip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 228, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
         __Pyx_XGOTREF(__pyx_cur_scope->__pyx_v_qualities);
         __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_v_qualities, ((PyObject*)__pyx_t_1));
@@ -3687,12 +5740,12 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       }
 
       /*else*/ {
-        __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_line, __pyx_n_s_rstrip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 132, __pyx_L1_error)
+        __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_cur_scope->__pyx_v_line, __pyx_n_s_rstrip); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 230, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_1);
-        __pyx_t_11 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 132, __pyx_L1_error)
+        __pyx_t_11 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__21, NULL); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 230, __pyx_L1_error)
         __Pyx_GOTREF(__pyx_t_11);
         __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-        if (!(likely(PyString_CheckExact(__pyx_t_11))||((__pyx_t_11) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_11)->tp_name), 0))) __PYX_ERR(0, 132, __pyx_L1_error)
+        if (!(likely(PyString_CheckExact(__pyx_t_11))||((__pyx_t_11) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "str", Py_TYPE(__pyx_t_11)->tp_name), 0))) __PYX_ERR(0, 230, __pyx_L1_error)
         __Pyx_XGOTREF(__pyx_cur_scope->__pyx_v_qualities);
         __Pyx_XDECREF_SET(__pyx_cur_scope->__pyx_v_qualities, ((PyObject*)__pyx_t_11));
         __Pyx_GIVEREF(__pyx_t_11);
@@ -3700,8 +5753,8 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       }
       __pyx_L18:;
 
-      if (unlikely(!__pyx_cur_scope->__pyx_v_sequence)) { __Pyx_RaiseUnboundLocalError("sequence"); __PYX_ERR(0, 133, __pyx_L1_error) }
-      __pyx_t_11 = PyTuple_New(3); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 133, __pyx_L1_error)
+      if (unlikely(!__pyx_cur_scope->__pyx_v_sequence)) { __Pyx_RaiseUnboundLocalError("sequence"); __PYX_ERR(0, 231, __pyx_L1_error) }
+      __pyx_t_11 = PyTuple_New(3); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 231, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_11);
       __Pyx_INCREF(__pyx_cur_scope->__pyx_v_name);
       __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_name);
@@ -3712,13 +5765,13 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       __Pyx_INCREF(__pyx_cur_scope->__pyx_v_qualities);
       __Pyx_GIVEREF(__pyx_cur_scope->__pyx_v_qualities);
       PyTuple_SET_ITEM(__pyx_t_11, 2, __pyx_cur_scope->__pyx_v_qualities);
-      __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 133, __pyx_L1_error)
+      __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 231, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_1);
-      __pyx_t_8 = __Pyx_PyBool_FromLong(__pyx_cur_scope->__pyx_v_second_header); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 133, __pyx_L1_error)
+      __pyx_t_8 = __Pyx_PyBool_FromLong(__pyx_cur_scope->__pyx_v_second_header); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 231, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
-      if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_second_header, __pyx_t_8) < 0) __PYX_ERR(0, 133, __pyx_L1_error)
+      if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_second_header, __pyx_t_8) < 0) __PYX_ERR(0, 231, __pyx_L1_error)
       __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_cur_scope->__pyx_v_sequence_class, __pyx_t_11, __pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 133, __pyx_L1_error)
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_cur_scope->__pyx_v_sequence_class, __pyx_t_11, __pyx_t_1); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 231, __pyx_L1_error)
       __Pyx_GOTREF(__pyx_t_8);
       __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
       __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
@@ -3726,7 +5779,7 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       __pyx_t_8 = 0;
       __Pyx_XGIVEREF(__pyx_t_2);
       __pyx_cur_scope->__pyx_t_0 = __pyx_t_2;
-      __pyx_cur_scope->__pyx_t_1 = __pyx_t_12;
+      __pyx_cur_scope->__pyx_t_1 = __pyx_t_10;
       __pyx_cur_scope->__pyx_t_2 = __pyx_t_13;
       __Pyx_XGIVEREF(__pyx_r);
       __Pyx_RefNannyFinishContext();
@@ -3737,9 +5790,9 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
       __pyx_t_2 = __pyx_cur_scope->__pyx_t_0;
       __pyx_cur_scope->__pyx_t_0 = 0;
       __Pyx_XGOTREF(__pyx_t_2);
-      __pyx_t_12 = __pyx_cur_scope->__pyx_t_1;
+      __pyx_t_10 = __pyx_cur_scope->__pyx_t_1;
       __pyx_t_13 = __pyx_cur_scope->__pyx_t_2;
-      if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 133, __pyx_L1_error)
+      if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 231, __pyx_L1_error)
 
       break;
       default: break;
@@ -3753,17 +5806,16 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
   __pyx_t_4 = ((__pyx_cur_scope->__pyx_v_i != 0) != 0);
   if (__pyx_t_4) {
 
-    __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 136, __pyx_L1_error)
+    __pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_FormatError); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 234, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_2);
-    __pyx_t_8 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__7, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 136, __pyx_L1_error)
+    __pyx_t_8 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_tuple__22, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 234, __pyx_L1_error)
     __Pyx_GOTREF(__pyx_t_8);
     __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
     __Pyx_Raise(__pyx_t_8, 0, 0, 0);
     __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
-    __PYX_ERR(0, 136, __pyx_L1_error)
+    __PYX_ERR(0, 234, __pyx_L1_error)
 
   }
-  CYTHON_MAYBE_UNUSED_VAR(__pyx_cur_scope);
 
 
   /* function exit code */
@@ -3778,7 +5830,7 @@ static PyObject *__pyx_gb_8cutadapt_6_seqio_11FastqReader_4generator(__pyx_Corou
   __Pyx_XDECREF(__pyx_t_8);
   __Pyx_XDECREF(__pyx_t_9);
   __Pyx_XDECREF(__pyx_t_11);
-  __Pyx_XDECREF(__pyx_t_14);
+  __Pyx_XDECREF(__pyx_t_15);
   __Pyx_AddTraceback("__iter__", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __pyx_L0:;
   __Pyx_XDECREF(__pyx_r); __pyx_r = 0;
@@ -4157,48 +6209,80 @@ static struct PyModuleDef __pyx_moduledef = {
 static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_kp_s_, __pyx_k_, sizeof(__pyx_k_), 0, 0, 1, 0},
   {&__pyx_kp_s_At_line_0_Sequence_descriptions, __pyx_k_At_line_0_Sequence_descriptions, sizeof(__pyx_k_At_line_0_Sequence_descriptions), 0, 0, 1, 0},
+  {&__pyx_kp_s_Expected_at_least_d_arguments, __pyx_k_Expected_at_least_d_arguments, sizeof(__pyx_k_Expected_at_least_d_arguments), 0, 0, 1, 0},
   {&__pyx_kp_s_FASTQ_file_ended_prematurely, __pyx_k_FASTQ_file_ended_prematurely, sizeof(__pyx_k_FASTQ_file_ended_prematurely), 0, 0, 1, 0},
   {&__pyx_n_s_FastqReader, __pyx_k_FastqReader, sizeof(__pyx_k_FastqReader), 0, 0, 1, 1},
   {&__pyx_n_s_FastqReader___init, __pyx_k_FastqReader___init, sizeof(__pyx_k_FastqReader___init), 0, 0, 1, 1},
   {&__pyx_n_s_FastqReader___iter, __pyx_k_FastqReader___iter, sizeof(__pyx_k_FastqReader___iter), 0, 0, 1, 1},
   {&__pyx_n_s_FormatError, __pyx_k_FormatError, sizeof(__pyx_k_FormatError), 0, 0, 1, 1},
+  {&__pyx_kp_s_Function_call_with_ambiguous_arg, __pyx_k_Function_call_with_ambiguous_arg, sizeof(__pyx_k_Function_call_with_ambiguous_arg), 0, 0, 1, 0},
   {&__pyx_kp_s_In_read_named_0_r_length_of_qual, __pyx_k_In_read_named_0_r_length_of_qual, sizeof(__pyx_k_In_read_named_0_r_length_of_qual), 0, 0, 1, 0},
   {&__pyx_kp_s_Line_0_in_FASTQ_file_is_expected, __pyx_k_Line_0_in_FASTQ_file_is_expected, sizeof(__pyx_k_Line_0_in_FASTQ_file_is_expected), 0, 0, 1, 0},
   {&__pyx_kp_s_Line_0_in_FASTQ_file_is_expected_2, __pyx_k_Line_0_in_FASTQ_file_is_expected_2, sizeof(__pyx_k_Line_0_in_FASTQ_file_is_expected_2), 0, 0, 1, 0},
+  {&__pyx_kp_s_No_matching_signature_found, __pyx_k_No_matching_signature_found, sizeof(__pyx_k_No_matching_signature_found), 0, 0, 1, 0},
   {&__pyx_n_s_NotImplementedError, __pyx_k_NotImplementedError, sizeof(__pyx_k_NotImplementedError), 0, 0, 1, 1},
   {&__pyx_kp_s_Reader_for_FASTQ_files_Does_not, __pyx_k_Reader_for_FASTQ_files_Does_not, sizeof(__pyx_k_Reader_for_FASTQ_files_Does_not), 0, 0, 1, 0},
   {&__pyx_n_s_SequenceReader, __pyx_k_SequenceReader, sizeof(__pyx_k_SequenceReader), 0, 0, 1, 1},
   {&__pyx_kp_s_Sequence_name_0_r_sequence_1_r, __pyx_k_Sequence_name_0_r_sequence_1_r, sizeof(__pyx_k_Sequence_name_0_r_sequence_1_r), 0, 0, 1, 0},
-  {&__pyx_kp_s__2, __pyx_k__2, sizeof(__pyx_k__2), 0, 0, 1, 0},
+  {&__pyx_n_s_TypeError, __pyx_k_TypeError, sizeof(__pyx_k_TypeError), 0, 0, 1, 1},
+  {&__pyx_kp_s__16, __pyx_k__16, sizeof(__pyx_k__16), 0, 0, 1, 0},
+  {&__pyx_kp_s__17, __pyx_k__17, sizeof(__pyx_k__17), 0, 0, 1, 0},
+  {&__pyx_kp_s__18, __pyx_k__18, sizeof(__pyx_k__18), 0, 0, 1, 0},
+  {&__pyx_kp_s__19, __pyx_k__19, sizeof(__pyx_k__19), 0, 0, 1, 0},
+  {&__pyx_kp_s__20, __pyx_k__20, sizeof(__pyx_k__20), 0, 0, 1, 0},
   {&__pyx_kp_s__3, __pyx_k__3, sizeof(__pyx_k__3), 0, 0, 1, 0},
-  {&__pyx_kp_s__4, __pyx_k__4, sizeof(__pyx_k__4), 0, 0, 1, 0},
-  {&__pyx_kp_s__5, __pyx_k__5, sizeof(__pyx_k__5), 0, 0, 1, 0},
   {&__pyx_n_s_args, __pyx_k_args, sizeof(__pyx_k_args), 0, 0, 1, 1},
+  {&__pyx_n_s_buf, __pyx_k_buf, sizeof(__pyx_k_buf), 0, 0, 1, 1},
+  {&__pyx_n_s_buf1, __pyx_k_buf1, sizeof(__pyx_k_buf1), 0, 0, 1, 1},
+  {&__pyx_n_s_buf2, __pyx_k_buf2, sizeof(__pyx_k_buf2), 0, 0, 1, 1},
+  {&__pyx_n_s_bytearray, __pyx_k_bytearray, sizeof(__pyx_k_bytearray), 0, 0, 1, 1},
+  {&__pyx_n_s_bytes, __pyx_k_bytes, sizeof(__pyx_k_bytes), 0, 0, 1, 1},
   {&__pyx_n_s_class, __pyx_k_class, sizeof(__pyx_k_class), 0, 0, 1, 1},
   {&__pyx_n_s_close, __pyx_k_close, sizeof(__pyx_k_close), 0, 0, 1, 1},
   {&__pyx_n_s_cutadapt__seqio, __pyx_k_cutadapt__seqio, sizeof(__pyx_k_cutadapt__seqio), 0, 0, 1, 1},
+  {&__pyx_n_s_data, __pyx_k_data, sizeof(__pyx_k_data), 0, 0, 1, 1},
+  {&__pyx_n_s_data1, __pyx_k_data1, sizeof(__pyx_k_data1), 0, 0, 1, 1},
+  {&__pyx_n_s_data2, __pyx_k_data2, sizeof(__pyx_k_data2), 0, 0, 1, 1},
+  {&__pyx_n_s_defaults, __pyx_k_defaults, sizeof(__pyx_k_defaults), 0, 0, 1, 1},
   {&__pyx_n_s_delivers_qualities, __pyx_k_delivers_qualities, sizeof(__pyx_k_delivers_qualities), 0, 0, 1, 1},
   {&__pyx_n_s_doc, __pyx_k_doc, sizeof(__pyx_k_doc), 0, 0, 1, 1},
+  {&__pyx_n_s_end, __pyx_k_end, sizeof(__pyx_k_end), 0, 0, 1, 1},
+  {&__pyx_n_s_end1, __pyx_k_end1, sizeof(__pyx_k_end1), 0, 0, 1, 1},
+  {&__pyx_n_s_end2, __pyx_k_end2, sizeof(__pyx_k_end2), 0, 0, 1, 1},
+  {&__pyx_n_s_fastq_head, __pyx_k_fastq_head, sizeof(__pyx_k_fastq_head), 0, 0, 1, 1},
   {&__pyx_n_s_file, __pyx_k_file, sizeof(__pyx_k_file), 0, 0, 1, 1},
   {&__pyx_n_s_file_2, __pyx_k_file_2, sizeof(__pyx_k_file_2), 0, 0, 1, 1},
   {&__pyx_n_s_format, __pyx_k_format, sizeof(__pyx_k_format), 0, 0, 1, 1},
+  {&__pyx_n_s_head, __pyx_k_head, sizeof(__pyx_k_head), 0, 0, 1, 1},
   {&__pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_k_home_marcel_scm_cutadapt_cutada, sizeof(__pyx_k_home_marcel_scm_cutadapt_cutada), 0, 0, 1, 0},
   {&__pyx_n_s_i, __pyx_k_i, sizeof(__pyx_k_i), 0, 0, 1, 1},
   {&__pyx_n_s_import, __pyx_k_import, sizeof(__pyx_k_import), 0, 0, 1, 1},
   {&__pyx_n_s_init, __pyx_k_init, sizeof(__pyx_k_init), 0, 0, 1, 1},
   {&__pyx_n_s_it, __pyx_k_it, sizeof(__pyx_k_it), 0, 0, 1, 1},
   {&__pyx_n_s_iter, __pyx_k_iter, sizeof(__pyx_k_iter), 0, 0, 1, 1},
+  {&__pyx_n_s_kwargs, __pyx_k_kwargs, sizeof(__pyx_k_kwargs), 0, 0, 1, 1},
+  {&__pyx_n_s_length, __pyx_k_length, sizeof(__pyx_k_length), 0, 0, 1, 1},
   {&__pyx_n_s_line, __pyx_k_line, sizeof(__pyx_k_line), 0, 0, 1, 1},
+  {&__pyx_n_s_linebreaks, __pyx_k_linebreaks, sizeof(__pyx_k_linebreaks), 0, 0, 1, 1},
+  {&__pyx_n_s_linebreaks_seen, __pyx_k_linebreaks_seen, sizeof(__pyx_k_linebreaks_seen), 0, 0, 1, 1},
+  {&__pyx_n_s_lines, __pyx_k_lines, sizeof(__pyx_k_lines), 0, 0, 1, 1},
   {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1},
   {&__pyx_n_s_match, __pyx_k_match, sizeof(__pyx_k_match), 0, 0, 1, 1},
   {&__pyx_n_s_metaclass, __pyx_k_metaclass, sizeof(__pyx_k_metaclass), 0, 0, 1, 1},
   {&__pyx_n_s_module, __pyx_k_module, sizeof(__pyx_k_module), 0, 0, 1, 1},
   {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1},
   {&__pyx_n_s_name2, __pyx_k_name2, sizeof(__pyx_k_name2), 0, 0, 1, 1},
+  {&__pyx_n_s_name_2, __pyx_k_name_2, sizeof(__pyx_k_name_2), 0, 0, 1, 1},
+  {&__pyx_n_s_pos, __pyx_k_pos, sizeof(__pyx_k_pos), 0, 0, 1, 1},
+  {&__pyx_n_s_pos1, __pyx_k_pos1, sizeof(__pyx_k_pos1), 0, 0, 1, 1},
+  {&__pyx_n_s_pos2, __pyx_k_pos2, sizeof(__pyx_k_pos2), 0, 0, 1, 1},
   {&__pyx_n_s_prepare, __pyx_k_prepare, sizeof(__pyx_k_prepare), 0, 0, 1, 1},
   {&__pyx_n_s_qualities, __pyx_k_qualities, sizeof(__pyx_k_qualities), 0, 0, 1, 1},
   {&__pyx_kp_s_qualities_0_r, __pyx_k_qualities_0_r, sizeof(__pyx_k_qualities_0_r), 0, 0, 1, 0},
   {&__pyx_n_s_qualname, __pyx_k_qualname, sizeof(__pyx_k_qualname), 0, 0, 1, 1},
+  {&__pyx_n_s_record_start, __pyx_k_record_start, sizeof(__pyx_k_record_start), 0, 0, 1, 1},
+  {&__pyx_n_s_record_start1, __pyx_k_record_start1, sizeof(__pyx_k_record_start1), 0, 0, 1, 1},
+  {&__pyx_n_s_record_start2, __pyx_k_record_start2, sizeof(__pyx_k_record_start2), 0, 0, 1, 1},
   {&__pyx_n_s_rstrip, __pyx_k_rstrip, sizeof(__pyx_k_rstrip), 0, 0, 1, 1},
   {&__pyx_n_s_second_header, __pyx_k_second_header, sizeof(__pyx_k_second_header), 0, 0, 1, 1},
   {&__pyx_n_s_self, __pyx_k_self, sizeof(__pyx_k_self), 0, 0, 1, 1},
@@ -4207,16 +6291,22 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_n_s_sequence, __pyx_k_sequence, sizeof(__pyx_k_sequence), 0, 0, 1, 1},
   {&__pyx_n_s_sequence_class, __pyx_k_sequence_class, sizeof(__pyx_k_sequence_class), 0, 0, 1, 1},
   {&__pyx_n_s_shorten, __pyx_k_shorten, sizeof(__pyx_k_shorten), 0, 0, 1, 1},
+  {&__pyx_n_s_signatures, __pyx_k_signatures, sizeof(__pyx_k_signatures), 0, 0, 1, 1},
+  {&__pyx_n_s_split, __pyx_k_split, sizeof(__pyx_k_split), 0, 0, 1, 1},
   {&__pyx_n_s_strip, __pyx_k_strip, sizeof(__pyx_k_strip), 0, 0, 1, 1},
   {&__pyx_n_s_super, __pyx_k_super, sizeof(__pyx_k_super), 0, 0, 1, 1},
   {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1},
   {&__pyx_n_s_throw, __pyx_k_throw, sizeof(__pyx_k_throw), 0, 0, 1, 1},
+  {&__pyx_n_s_two_fastq_heads, __pyx_k_two_fastq_heads, sizeof(__pyx_k_two_fastq_heads), 0, 0, 1, 1},
   {&__pyx_n_s_xopen, __pyx_k_xopen, sizeof(__pyx_k_xopen), 0, 0, 1, 1},
+  {&__pyx_n_s_zip, __pyx_k_zip, sizeof(__pyx_k_zip), 0, 0, 1, 1},
   {0, 0, 0, 0, 0, 0, 0}
 };
 static int __Pyx_InitCachedBuiltins(void) {
-  __pyx_builtin_NotImplementedError = __Pyx_GetBuiltinName(__pyx_n_s_NotImplementedError); if (!__pyx_builtin_NotImplementedError) __PYX_ERR(0, 66, __pyx_L1_error)
-  __pyx_builtin_super = __Pyx_GetBuiltinName(__pyx_n_s_super); if (!__pyx_builtin_super) __PYX_ERR(0, 82, __pyx_L1_error)
+  __pyx_builtin_TypeError = __Pyx_GetBuiltinName(__pyx_n_s_TypeError); if (!__pyx_builtin_TypeError) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_builtin_zip = __Pyx_GetBuiltinName(__pyx_n_s_zip); if (!__pyx_builtin_zip) __PYX_ERR(0, 20, __pyx_L1_error)
+  __pyx_builtin_NotImplementedError = __Pyx_GetBuiltinName(__pyx_n_s_NotImplementedError); if (!__pyx_builtin_NotImplementedError) __PYX_ERR(0, 164, __pyx_L1_error)
+  __pyx_builtin_super = __Pyx_GetBuiltinName(__pyx_n_s_super); if (!__pyx_builtin_super) __PYX_ERR(0, 180, __pyx_L1_error)
   return 0;
   __pyx_L1_error:;
   return -1;
@@ -4226,23 +6316,77 @@ static int __Pyx_InitCachedConstants(void) {
   __Pyx_RefNannyDeclarations
   __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0);
 
-  __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_s__3); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(0, 132, __pyx_L1_error)
+  __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_s_); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__2);
+  __Pyx_GIVEREF(__pyx_tuple__2);
+  __pyx_tuple__4 = PyTuple_Pack(1, __pyx_kp_s__3); if (unlikely(!__pyx_tuple__4)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__4);
+  __Pyx_GIVEREF(__pyx_tuple__4);
+  __pyx_tuple__5 = PyTuple_Pack(1, __pyx_kp_s_No_matching_signature_found); if (unlikely(!__pyx_tuple__5)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__5);
+  __Pyx_GIVEREF(__pyx_tuple__5);
+  __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_s_Function_call_with_ambiguous_arg); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(0, 20, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__6);
   __Pyx_GIVEREF(__pyx_tuple__6);
 
-  __pyx_tuple__7 = PyTuple_Pack(1, __pyx_kp_s_FASTQ_file_ended_prematurely); if (unlikely(!__pyx_tuple__7)) __PYX_ERR(0, 136, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_tuple__7);
-  __Pyx_GIVEREF(__pyx_tuple__7);
-
-  __pyx_tuple__8 = PyTuple_Pack(3, __pyx_n_s_self, __pyx_n_s_file, __pyx_n_s_sequence_class); if (unlikely(!__pyx_tuple__8)) __PYX_ERR(0, 77, __pyx_L1_error)
+  __pyx_tuple__8 = PyTuple_Pack(1, __pyx_kp_s_); if (unlikely(!__pyx_tuple__8)) __PYX_ERR(0, 38, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__8);
   __Pyx_GIVEREF(__pyx_tuple__8);
-  __pyx_codeobj__9 = (PyObject*)__Pyx_PyCode_New(3, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__8, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_init, 77, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__9)) __PYX_ERR(0, 77, __pyx_L1_error)
-
-  __pyx_tuple__10 = PyTuple_Pack(11, __pyx_n_s_self, __pyx_n_s_i, __pyx_n_s_strip, __pyx_n_s_line, __pyx_n_s_name, __pyx_n_s_qualities, __pyx_n_s_sequence, __pyx_n_s_name2, __pyx_n_s_sequence_class, __pyx_n_s_it, __pyx_n_s_second_header); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(0, 86, __pyx_L1_error)
+  __pyx_tuple__9 = PyTuple_Pack(1, __pyx_kp_s__3); if (unlikely(!__pyx_tuple__9)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__9);
+  __Pyx_GIVEREF(__pyx_tuple__9);
+  __pyx_tuple__10 = PyTuple_Pack(1, __pyx_kp_s_No_matching_signature_found); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(0, 38, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__10);
   __Pyx_GIVEREF(__pyx_tuple__10);
-  __pyx_codeobj__11 = (PyObject*)__Pyx_PyCode_New(1, 0, 11, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__10, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_iter, 86, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__11)) __PYX_ERR(0, 86, __pyx_L1_error)
+  __pyx_tuple__11 = PyTuple_Pack(1, __pyx_kp_s_Function_call_with_ambiguous_arg); if (unlikely(!__pyx_tuple__11)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__11);
+  __Pyx_GIVEREF(__pyx_tuple__11);
+
+  __pyx_tuple__12 = PyTuple_Pack(1, __pyx_kp_s_); if (unlikely(!__pyx_tuple__12)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__12);
+  __Pyx_GIVEREF(__pyx_tuple__12);
+  __pyx_tuple__13 = PyTuple_Pack(1, __pyx_kp_s__3); if (unlikely(!__pyx_tuple__13)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__13);
+  __Pyx_GIVEREF(__pyx_tuple__13);
+  __pyx_tuple__14 = PyTuple_Pack(1, __pyx_kp_s_No_matching_signature_found); if (unlikely(!__pyx_tuple__14)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__14);
+  __Pyx_GIVEREF(__pyx_tuple__14);
+  __pyx_tuple__15 = PyTuple_Pack(1, __pyx_kp_s_Function_call_with_ambiguous_arg); if (unlikely(!__pyx_tuple__15)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__15);
+  __Pyx_GIVEREF(__pyx_tuple__15);
+
+  __pyx_tuple__21 = PyTuple_Pack(1, __pyx_kp_s__18); if (unlikely(!__pyx_tuple__21)) __PYX_ERR(0, 230, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__21);
+  __Pyx_GIVEREF(__pyx_tuple__21);
+
+  __pyx_tuple__22 = PyTuple_Pack(1, __pyx_kp_s_FASTQ_file_ended_prematurely); if (unlikely(!__pyx_tuple__22)) __PYX_ERR(0, 234, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__22);
+  __Pyx_GIVEREF(__pyx_tuple__22);
+
+  __pyx_tuple__23 = PyTuple_Pack(6, __pyx_n_s_buf, __pyx_n_s_lines, __pyx_n_s_pos, __pyx_n_s_linebreaks_seen, __pyx_n_s_length, __pyx_n_s_data); if (unlikely(!__pyx_tuple__23)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__23);
+  __Pyx_GIVEREF(__pyx_tuple__23);
+  __pyx_codeobj__24 = (PyObject*)__Pyx_PyCode_New(2, 0, 6, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__23, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_head, 20, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__24)) __PYX_ERR(0, 20, __pyx_L1_error)
+
+  __pyx_tuple__25 = PyTuple_Pack(7, __pyx_n_s_buf, __pyx_n_s_end, __pyx_n_s_pos, __pyx_n_s_linebreaks, __pyx_n_s_length, __pyx_n_s_data, __pyx_n_s_record_start); if (unlikely(!__pyx_tuple__25)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__25);
+  __Pyx_GIVEREF(__pyx_tuple__25);
+  __pyx_codeobj__26 = (PyObject*)__Pyx_PyCode_New(2, 0, 7, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__25, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_fastq_head, 38, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__26)) __PYX_ERR(0, 38, __pyx_L1_error)
+
+  __pyx_tuple__27 = PyTuple_Pack(11, __pyx_n_s_buf1, __pyx_n_s_buf2, __pyx_n_s_end1, __pyx_n_s_end2, __pyx_n_s_pos1, __pyx_n_s_pos2, __pyx_n_s_linebreaks, __pyx_n_s_data1, __pyx_n_s_data2, __pyx_n_s_record_start1, __pyx_n_s_record_start2); if (unlikely(!__pyx_tuple__27)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__27);
+  __Pyx_GIVEREF(__pyx_tuple__27);
+  __pyx_codeobj__28 = (PyObject*)__Pyx_PyCode_New(4, 0, 11, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__27, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_two_fastq_heads, 69, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__28)) __PYX_ERR(0, 69, __pyx_L1_error)
+
+  __pyx_tuple__29 = PyTuple_Pack(3, __pyx_n_s_self, __pyx_n_s_file, __pyx_n_s_sequence_class); if (unlikely(!__pyx_tuple__29)) __PYX_ERR(0, 175, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__29);
+  __Pyx_GIVEREF(__pyx_tuple__29);
+  __pyx_codeobj__30 = (PyObject*)__Pyx_PyCode_New(3, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__29, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_init, 175, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__30)) __PYX_ERR(0, 175, __pyx_L1_error)
+
+  __pyx_tuple__31 = PyTuple_Pack(11, __pyx_n_s_self, __pyx_n_s_i, __pyx_n_s_strip, __pyx_n_s_line, __pyx_n_s_name, __pyx_n_s_qualities, __pyx_n_s_sequence, __pyx_n_s_name2, __pyx_n_s_sequence_class, __pyx_n_s_it, __pyx_n_s_second_header); if (unlikely(!__pyx_tuple__31)) __PYX_ERR(0, 184, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_tuple__31);
+  __Pyx_GIVEREF(__pyx_tuple__31);
+  __pyx_codeobj__32 = (PyObject*)__Pyx_PyCode_New(1, 0, 11, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__31, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_marcel_scm_cutadapt_cutada, __pyx_n_s_iter, 184, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__32)) __PYX_ERR(0, 184, __pyx_L1_error)
   __Pyx_RefNannyFinishContext();
   return 0;
   __pyx_L1_error:;
@@ -4269,6 +6413,13 @@ PyMODINIT_FUNC PyInit__seqio(void)
   PyObject *__pyx_t_2 = NULL;
   PyObject *__pyx_t_3 = NULL;
   PyObject *__pyx_t_4 = NULL;
+  PyObject *__pyx_t_5 = NULL;
+  PyObject *__pyx_t_6 = NULL;
+  PyObject *__pyx_t_7 = NULL;
+  PyObject *__pyx_t_8 = NULL;
+  PyObject *__pyx_t_9 = NULL;
+  PyObject *__pyx_t_10 = NULL;
+  PyObject *__pyx_t_11 = NULL;
   __Pyx_RefNannyDeclarations
   #if CYTHON_REFNANNY
   __Pyx_RefNanny = __Pyx_RefNannyImportAPI("refnanny");
@@ -4344,11 +6495,11 @@ PyMODINIT_FUNC PyInit__seqio(void)
   /*--- Variable export code ---*/
   /*--- Function export code ---*/
   /*--- Type init code ---*/
-  if (PyType_Ready(&__pyx_type_8cutadapt_6_seqio_Sequence) < 0) __PYX_ERR(0, 8, __pyx_L1_error)
+  if (PyType_Ready(&__pyx_type_8cutadapt_6_seqio_Sequence) < 0) __PYX_ERR(0, 106, __pyx_L1_error)
   __pyx_type_8cutadapt_6_seqio_Sequence.tp_print = 0;
   #if CYTHON_COMPILING_IN_CPYTHON
   {
-    PyObject *wrapper = PyObject_GetAttrString((PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence, "__init__"); if (unlikely(!wrapper)) __PYX_ERR(0, 8, __pyx_L1_error)
+    PyObject *wrapper = PyObject_GetAttrString((PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence, "__init__"); if (unlikely(!wrapper)) __PYX_ERR(0, 106, __pyx_L1_error)
     if (Py_TYPE(wrapper) == &PyWrapperDescr_Type) {
       __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence___init__ = *((PyWrapperDescrObject *)wrapper)->d_base;
       __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence___init__.doc = __pyx_doc_8cutadapt_6_seqio_8Sequence___init__;
@@ -4358,7 +6509,7 @@ PyMODINIT_FUNC PyInit__seqio(void)
   #endif
   #if CYTHON_COMPILING_IN_CPYTHON
   {
-    PyObject *wrapper = PyObject_GetAttrString((PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence, "__getitem__"); if (unlikely(!wrapper)) __PYX_ERR(0, 8, __pyx_L1_error)
+    PyObject *wrapper = PyObject_GetAttrString((PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence, "__getitem__"); if (unlikely(!wrapper)) __PYX_ERR(0, 106, __pyx_L1_error)
     if (Py_TYPE(wrapper) == &PyWrapperDescr_Type) {
       __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence_2__getitem__ = *((PyWrapperDescrObject *)wrapper)->d_base;
       __pyx_wrapperbase_8cutadapt_6_seqio_8Sequence_2__getitem__.doc = __pyx_doc_8cutadapt_6_seqio_8Sequence_2__getitem__;
@@ -4366,9 +6517,9 @@ PyMODINIT_FUNC PyInit__seqio(void)
     }
   }
   #endif
-  if (PyObject_SetAttrString(__pyx_m, "Sequence", (PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence) < 0) __PYX_ERR(0, 8, __pyx_L1_error)
+  if (PyObject_SetAttrString(__pyx_m, "Sequence", (PyObject *)&__pyx_type_8cutadapt_6_seqio_Sequence) < 0) __PYX_ERR(0, 106, __pyx_L1_error)
   __pyx_ptype_8cutadapt_6_seqio_Sequence = &__pyx_type_8cutadapt_6_seqio_Sequence;
-  if (PyType_Ready(&__pyx_type_8cutadapt_6_seqio___pyx_scope_struct____iter__) < 0) __PYX_ERR(0, 86, __pyx_L1_error)
+  if (PyType_Ready(&__pyx_type_8cutadapt_6_seqio___pyx_scope_struct____iter__) < 0) __PYX_ERR(0, 184, __pyx_L1_error)
   __pyx_type_8cutadapt_6_seqio___pyx_scope_struct____iter__.tp_print = 0;
   __pyx_ptype_8cutadapt_6_seqio___pyx_scope_struct____iter__ = &__pyx_type_8cutadapt_6_seqio___pyx_scope_struct____iter__;
   /*--- Type import code ---*/
@@ -4421,45 +6572,125 @@ PyMODINIT_FUNC PyInit__seqio(void)
   __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
   __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
 
-  __pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s_SequenceReader); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 73, __pyx_L1_error)
+  __pyx_t_1 = PyDict_New(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 20, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_2 = PyTuple_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 73, __pyx_L1_error)
+  __pyx_t_2 = __pyx_FusedFunction_NewEx(&__pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_7head, 0, __pyx_n_s_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__24)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_2);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_2, __pyx_empty_tuple);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_bytes, __pyx_t_2) < 0) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
+  __pyx_t_2 = __pyx_FusedFunction_NewEx(&__pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_9head, 0, __pyx_n_s_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__24)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_2);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_2, __pyx_empty_tuple);
+  if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_bytearray, __pyx_t_2) < 0) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
+  __pyx_t_2 = __pyx_FusedFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_1head, 0, __pyx_n_s_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__24)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 20, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_2);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_2, __pyx_empty_tuple);
+  ((__pyx_FusedFunctionObject *) __pyx_t_2)->__signatures__ = __pyx_t_1;
   __Pyx_GIVEREF(__pyx_t_1);
-  PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_1);
-  __pyx_t_1 = 0;
-  __pyx_t_1 = __Pyx_CalculateMetaclass(NULL, __pyx_t_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 73, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_1);
-  __pyx_t_3 = __Pyx_Py3MetaclassPrepare(__pyx_t_1, __pyx_t_2, __pyx_n_s_FastqReader, __pyx_n_s_FastqReader, (PyObject *) NULL, __pyx_n_s_cutadapt__seqio, __pyx_kp_s_Reader_for_FASTQ_files_Does_not); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 73, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_3);
-
-  __pyx_t_4 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_11FastqReader_1__init__, 0, __pyx_n_s_FastqReader___init, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__9)); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 77, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_4);
-  if (!__Pyx_CyFunction_InitDefaults(__pyx_t_4, sizeof(__pyx_defaults), 1)) __PYX_ERR(0, 77, __pyx_L1_error)
-  __Pyx_INCREF(((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence));
-  __Pyx_CyFunction_Defaults(__pyx_defaults, __pyx_t_4)->__pyx_arg_sequence_class = ((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence);
-  __Pyx_GIVEREF(__pyx_ptype_8cutadapt_6_seqio_Sequence);
-  __Pyx_CyFunction_SetDefaultsGetter(__pyx_t_4, __pyx_pf_8cutadapt_6_seqio___defaults__);
-  if (PyObject_SetItem(__pyx_t_3, __pyx_n_s_init, __pyx_t_4) < 0) __PYX_ERR(0, 77, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_head, __pyx_t_2) < 0) __PYX_ERR(0, 20, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
 
-  __pyx_t_4 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_11FastqReader_3__iter__, 0, __pyx_n_s_FastqReader___iter, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__11)); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 86, __pyx_L1_error)
+  __pyx_t_3 = __Pyx_PyInt_From_long(-1L); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_3);
+  __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 38, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_t_4);
-  if (PyObject_SetItem(__pyx_t_3, __pyx_n_s_iter, __pyx_t_4) < 0) __PYX_ERR(0, 86, __pyx_L1_error)
+  __Pyx_GIVEREF(__pyx_t_3);
+  PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_3);
+  __pyx_t_3 = 0;
+  __pyx_t_3 = __Pyx_PyInt_From_long(-1L); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_3);
+  __pyx_k__7 = __pyx_t_3;
+  __Pyx_GIVEREF(__pyx_t_3);
+  __pyx_t_3 = 0;
+  __pyx_t_3 = PyDict_New(); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_3);
+  __pyx_t_5 = __pyx_FusedFunction_NewEx(&__pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_13fastq_head, 0, __pyx_n_s_fastq_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__26)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  if (!__Pyx_CyFunction_InitDefaults(__pyx_t_5, sizeof(__pyx_defaults2), 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_CyFunction_Defaults(__pyx_defaults2, __pyx_t_5)->__pyx_arg_end = -1L;
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_5, __pyx_t_4);
+  __Pyx_CyFunction_SetDefaultsGetter(__pyx_t_5, __pyx_pf_8cutadapt_6_seqio_28__defaults__);
+  if (PyDict_SetItem(__pyx_t_3, __pyx_n_s_bytes, __pyx_t_5) < 0) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+  __pyx_t_5 = __pyx_FusedFunction_NewEx(&__pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_15fastq_head, 0, __pyx_n_s_fastq_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__26)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  if (!__Pyx_CyFunction_InitDefaults(__pyx_t_5, sizeof(__pyx_defaults3), 0)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_CyFunction_Defaults(__pyx_defaults3, __pyx_t_5)->__pyx_arg_end = -1L;
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_5, __pyx_t_4);
+  __Pyx_CyFunction_SetDefaultsGetter(__pyx_t_5, __pyx_pf_8cutadapt_6_seqio_30__defaults__);
+  if (PyDict_SetItem(__pyx_t_3, __pyx_n_s_bytearray, __pyx_t_5) < 0) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+  __pyx_t_5 = __pyx_FusedFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_3fastq_head, 0, __pyx_n_s_fastq_head, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__26)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_5);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_5, __pyx_t_4);
+  __Pyx_CyFunction_SetDefaultsGetter(__pyx_t_5, __pyx_pf_8cutadapt_6_seqio_28__defaults__);
+  ((__pyx_FusedFunctionObject *) __pyx_t_5)->__signatures__ = __pyx_t_3;
+  __Pyx_GIVEREF(__pyx_t_3);
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_fastq_head, __pyx_t_5) < 0) __PYX_ERR(0, 38, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
   __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 
-  __pyx_t_4 = __Pyx_Py3ClassCreate(__pyx_t_1, __pyx_n_s_FastqReader, __pyx_t_2, __pyx_t_3, NULL, 0, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 73, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_4);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_FastqReader, __pyx_t_4) < 0) __PYX_ERR(0, 73, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-  __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
-  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
-  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
+  __pyx_t_6 = PyDict_New(); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_6);
+  __pyx_t_7 = __pyx_FusedFunction_NewEx(&__pyx_fuse_0__pyx_mdef_8cutadapt_6_seqio_19two_fastq_heads, 0, __pyx_n_s_two_fastq_heads, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__28)); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_7);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_7, __pyx_empty_tuple);
+  if (PyDict_SetItem(__pyx_t_6, __pyx_n_s_bytes, __pyx_t_7) < 0) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
+  __pyx_t_7 = __pyx_FusedFunction_NewEx(&__pyx_fuse_1__pyx_mdef_8cutadapt_6_seqio_21two_fastq_heads, 0, __pyx_n_s_two_fastq_heads, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__28)); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_7);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_7, __pyx_empty_tuple);
+  if (PyDict_SetItem(__pyx_t_6, __pyx_n_s_bytearray, __pyx_t_7) < 0) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
+  __pyx_t_7 = __pyx_FusedFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_5two_fastq_heads, 0, __pyx_n_s_two_fastq_heads, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__28)); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_7);
+  __Pyx_CyFunction_SetDefaultsTuple(__pyx_t_7, __pyx_empty_tuple);
+  ((__pyx_FusedFunctionObject *) __pyx_t_7)->__signatures__ = __pyx_t_6;
+  __Pyx_GIVEREF(__pyx_t_6);
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_two_fastq_heads, __pyx_t_7) < 0) __PYX_ERR(0, 69, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
 
-  __pyx_t_2 = PyDict_New(); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 1, __pyx_L1_error)
-  __Pyx_GOTREF(__pyx_t_2);
-  if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_2) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
-  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
+  __pyx_t_8 = __Pyx_GetModuleGlobalName(__pyx_n_s_SequenceReader); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_8);
+  __pyx_t_9 = PyTuple_New(1); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_9);
+  __Pyx_GIVEREF(__pyx_t_8);
+  PyTuple_SET_ITEM(__pyx_t_9, 0, __pyx_t_8);
+  __pyx_t_8 = 0;
+  __pyx_t_8 = __Pyx_CalculateMetaclass(NULL, __pyx_t_9); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_8);
+  __pyx_t_10 = __Pyx_Py3MetaclassPrepare(__pyx_t_8, __pyx_t_9, __pyx_n_s_FastqReader, __pyx_n_s_FastqReader, (PyObject *) NULL, __pyx_n_s_cutadapt__seqio, __pyx_kp_s_Reader_for_FASTQ_files_Does_not); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_10);
+
+  __pyx_t_11 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_11FastqReader_1__init__, 0, __pyx_n_s_FastqReader___init, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__30)); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 175, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_11);
+  if (!__Pyx_CyFunction_InitDefaults(__pyx_t_11, sizeof(__pyx_defaults4), 1)) __PYX_ERR(0, 175, __pyx_L1_error)
+  __Pyx_INCREF(((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence));
+  __Pyx_CyFunction_Defaults(__pyx_defaults4, __pyx_t_11)->__pyx_arg_sequence_class = ((PyObject *)__pyx_ptype_8cutadapt_6_seqio_Sequence);
+  __Pyx_GIVEREF(__pyx_ptype_8cutadapt_6_seqio_Sequence);
+  __Pyx_CyFunction_SetDefaultsGetter(__pyx_t_11, __pyx_pf_8cutadapt_6_seqio_11FastqReader_5__defaults__);
+  if (PyObject_SetItem(__pyx_t_10, __pyx_n_s_init, __pyx_t_11) < 0) __PYX_ERR(0, 175, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
+
+  __pyx_t_11 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8cutadapt_6_seqio_11FastqReader_3__iter__, 0, __pyx_n_s_FastqReader___iter, NULL, __pyx_n_s_cutadapt__seqio, __pyx_d, ((PyObject *)__pyx_codeobj__32)); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 184, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_11);
+  if (PyObject_SetItem(__pyx_t_10, __pyx_n_s_iter, __pyx_t_11) < 0) __PYX_ERR(0, 184, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
+
+  __pyx_t_11 = __Pyx_Py3ClassCreate(__pyx_t_8, __pyx_n_s_FastqReader, __pyx_t_9, __pyx_t_10, NULL, 0, 1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_11);
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_FastqReader, __pyx_t_11) < 0) __PYX_ERR(0, 171, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0;
+  __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
+  __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+  __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
+
+  __pyx_t_9 = PyDict_New(); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 1, __pyx_L1_error)
+  __Pyx_GOTREF(__pyx_t_9);
+  if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_9) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
+  __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
 
   /*--- Wrapped vars code ---*/
 
@@ -4469,6 +6700,13 @@ PyMODINIT_FUNC PyInit__seqio(void)
   __Pyx_XDECREF(__pyx_t_2);
   __Pyx_XDECREF(__pyx_t_3);
   __Pyx_XDECREF(__pyx_t_4);
+  __Pyx_XDECREF(__pyx_t_5);
+  __Pyx_XDECREF(__pyx_t_6);
+  __Pyx_XDECREF(__pyx_t_7);
+  __Pyx_XDECREF(__pyx_t_8);
+  __Pyx_XDECREF(__pyx_t_9);
+  __Pyx_XDECREF(__pyx_t_10);
+  __Pyx_XDECREF(__pyx_t_11);
   if (__pyx_m) {
     if (__pyx_d) {
       __Pyx_AddTraceback("init cutadapt._seqio", __pyx_clineno, __pyx_lineno, __pyx_filename);
@@ -4660,191 +6898,89 @@ bad:
     return -1;
 }
 
-/* ArgTypeTest */
-static void __Pyx_RaiseArgumentTypeInvalid(const char* name, PyObject *obj, PyTypeObject *type) {
-    PyErr_Format(PyExc_TypeError,
-        "Argument '%.200s' has incorrect type (expected %.200s, got %.200s)",
-        name, type->tp_name, Py_TYPE(obj)->tp_name);
+/* GetItemInt */
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) {
+    PyObject *r;
+    if (!j) return NULL;
+    r = PyObject_GetItem(o, j);
+    Py_DECREF(j);
+    return r;
 }
-static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed,
-    const char *name, int exact)
-{
-    if (unlikely(!type)) {
-        PyErr_SetString(PyExc_SystemError, "Missing type object");
-        return 0;
-    }
-    if (none_allowed && obj == Py_None) return 1;
-    else if (exact) {
-        if (likely(Py_TYPE(obj) == type)) return 1;
-        #if PY_MAJOR_VERSION == 2
-        else if ((type == &PyBaseString_Type) && likely(__Pyx_PyBaseString_CheckExact(obj))) return 1;
-        #endif
-    }
-    else {
-        if (likely(PyObject_TypeCheck(obj, type))) return 1;
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
+                                                              CYTHON_NCP_UNUSED int wraparound,
+                                                              CYTHON_NCP_UNUSED int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
+    if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
+        PyObject *r = PyList_GET_ITEM(o, i);
+        Py_INCREF(r);
+        return r;
     }
-    __Pyx_RaiseArgumentTypeInvalid(name, obj, type);
-    return 0;
-}
-
-/* GetModuleGlobalName */
-static CYTHON_INLINE PyObject *__Pyx_GetModuleGlobalName(PyObject *name) {
-    PyObject *result;
-#if !CYTHON_AVOID_BORROWED_REFS
-    result = PyDict_GetItem(__pyx_d, name);
-    if (likely(result)) {
-        Py_INCREF(result);
-    } else {
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
 #else
-    result = PyObject_GetItem(__pyx_d, name);
-    if (!result) {
-        PyErr_Clear();
+    return PySequence_GetItem(o, i);
 #endif
-        result = __Pyx_GetBuiltinName(name);
-    }
-    return result;
-}
-
-/* PyCFunctionFastCall */
-  #if CYTHON_FAST_PYCCALL
-static CYTHON_INLINE PyObject * __Pyx_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, Py_ssize_t nargs) {
-    PyCFunctionObject *func = (PyCFunctionObject*)func_obj;
-    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
-    PyObject *self = PyCFunction_GET_SELF(func);
-    assert(PyCFunction_Check(func));
-    assert(METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)));
-    assert(nargs >= 0);
-    assert(nargs == 0 || args != NULL);
-    /* _PyCFunction_FastCallDict() must not be called with an exception set,
-       because it may clear it (directly or indirectly) and so the
-       caller loses its exception */
-    assert(!PyErr_Occurred());
-    return (*((__Pyx_PyCFunctionFast)meth)) (self, args, nargs, NULL);
-}
-#endif  // CYTHON_FAST_PYCCALL
-
-/* PyFunctionFastCall */
-  #if CYTHON_FAST_PYCALL
-#include "frameobject.h"
-static PyObject* __Pyx_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t na,
-                                               PyObject *globals) {
-    PyFrameObject *f;
-    PyThreadState *tstate = PyThreadState_GET();
-    PyObject **fastlocals;
-    Py_ssize_t i;
-    PyObject *result;
-    assert(globals != NULL);
-    /* XXX Perhaps we should create a specialized
-       PyFrame_New() that doesn't take locals, but does
-       take builtins without sanity checking them.
-       */
-    assert(tstate != NULL);
-    f = PyFrame_New(tstate, co, globals, NULL);
-    if (f == NULL) {
-        return NULL;
-    }
-    fastlocals = f->f_localsplus;
-    for (i = 0; i < na; i++) {
-        Py_INCREF(*args);
-        fastlocals[i] = *args++;
-    }
-    result = PyEval_EvalFrameEx(f,0);
-    ++tstate->recursion_depth;
-    Py_DECREF(f);
-    --tstate->recursion_depth;
-    return result;
 }
-#if 1 || PY_VERSION_HEX < 0x030600B1
-static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs) {
-    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
-    PyObject *globals = PyFunction_GET_GLOBALS(func);
-    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
-    PyObject *closure;
-#if PY_MAJOR_VERSION >= 3
-    PyObject *kwdefs;
-#endif
-    PyObject *kwtuple, **k;
-    PyObject **d;
-    Py_ssize_t nd;
-    Py_ssize_t nk;
-    PyObject *result;
-    assert(kwargs == NULL || PyDict_Check(kwargs));
-    nk = kwargs ? PyDict_Size(kwargs) : 0;
-    if (Py_EnterRecursiveCall((char*)" while calling a Python object")) {
-        return NULL;
-    }
-    if (
-#if PY_MAJOR_VERSION >= 3
-            co->co_kwonlyargcount == 0 &&
-#endif
-            likely(kwargs == NULL || nk == 0) &&
-            co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
-        if (argdefs == NULL && co->co_argcount == nargs) {
-            result = __Pyx_PyFunction_FastCallNoKw(co, args, nargs, globals);
-            goto done;
-        }
-        else if (nargs == 0 && argdefs != NULL
-                 && co->co_argcount == Py_SIZE(argdefs)) {
-            /* function called with no arguments, but all parameters have
-               a default value: use default values as arguments .*/
-            args = &PyTuple_GET_ITEM(argdefs, 0);
-            result =__Pyx_PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs), globals);
-            goto done;
-        }
-    }
-    if (kwargs != NULL) {
-        Py_ssize_t pos, i;
-        kwtuple = PyTuple_New(2 * nk);
-        if (kwtuple == NULL) {
-            result = NULL;
-            goto done;
-        }
-        k = &PyTuple_GET_ITEM(kwtuple, 0);
-        pos = i = 0;
-        while (PyDict_Next(kwargs, &pos, &k[i], &k[i+1])) {
-            Py_INCREF(k[i]);
-            Py_INCREF(k[i+1]);
-            i += 2;
-        }
-        nk = i / 2;
-    }
-    else {
-        kwtuple = NULL;
-        k = NULL;
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
+                                                              CYTHON_NCP_UNUSED int wraparound,
+                                                              CYTHON_NCP_UNUSED int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
+    if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
+        PyObject *r = PyTuple_GET_ITEM(o, i);
+        Py_INCREF(r);
+        return r;
     }
-    closure = PyFunction_GET_CLOSURE(func);
-#if PY_MAJOR_VERSION >= 3
-    kwdefs = PyFunction_GET_KW_DEFAULTS(func);
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
+#else
+    return PySequence_GetItem(o, i);
 #endif
-    if (argdefs != NULL) {
-        d = &PyTuple_GET_ITEM(argdefs, 0);
-        nd = Py_SIZE(argdefs);
+}
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list,
+                                                     CYTHON_NCP_UNUSED int wraparound,
+                                                     CYTHON_NCP_UNUSED int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (is_list || PyList_CheckExact(o)) {
+        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
+        if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
+            PyObject *r = PyList_GET_ITEM(o, n);
+            Py_INCREF(r);
+            return r;
+        }
     }
-    else {
-        d = NULL;
-        nd = 0;
+    else if (PyTuple_CheckExact(o)) {
+        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o);
+        if ((!boundscheck) || likely((n >= 0) & (n < PyTuple_GET_SIZE(o)))) {
+            PyObject *r = PyTuple_GET_ITEM(o, n);
+            Py_INCREF(r);
+            return r;
+        }
+    } else {
+        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
+        if (likely(m && m->sq_item)) {
+            if (wraparound && unlikely(i < 0) && likely(m->sq_length)) {
+                Py_ssize_t l = m->sq_length(o);
+                if (likely(l >= 0)) {
+                    i += l;
+                } else {
+                    if (!PyErr_ExceptionMatches(PyExc_OverflowError))
+                        return NULL;
+                    PyErr_Clear();
+                }
+            }
+            return m->sq_item(o, i);
+        }
     }
-#if PY_MAJOR_VERSION >= 3
-    result = PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL,
-                               args, nargs,
-                               k, (int)nk,
-                               d, (int)nd, kwdefs, closure);
 #else
-    result = PyEval_EvalCodeEx(co, globals, (PyObject *)NULL,
-                               args, nargs,
-                               k, (int)nk,
-                               d, (int)nd, closure);
+    if (is_list || PySequence_Check(o)) {
+        return PySequence_GetItem(o, i);
+    }
 #endif
-    Py_XDECREF(kwtuple);
-done:
-    Py_LeaveRecursiveCall();
-    return result;
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
 }
-#endif  // CPython < 3.6
-#endif  // CYTHON_FAST_PYCALL
 
 /* PyObjectCall */
-  #if CYTHON_COMPILING_IN_CPYTHON
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw) {
     PyObject *result;
     ternaryfunc call = func->ob_type->tp_call;
@@ -4863,72 +6999,8 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg
 }
 #endif
 
-/* PyObjectCallMethO */
-  #if CYTHON_COMPILING_IN_CPYTHON
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) {
-    PyObject *self, *result;
-    PyCFunction cfunc;
-    cfunc = PyCFunction_GET_FUNCTION(func);
-    self = PyCFunction_GET_SELF(func);
-    if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object")))
-        return NULL;
-    result = cfunc(self, arg);
-    Py_LeaveRecursiveCall();
-    if (unlikely(!result) && unlikely(!PyErr_Occurred())) {
-        PyErr_SetString(
-            PyExc_SystemError,
-            "NULL result without error in PyObject_Call");
-    }
-    return result;
-}
-#endif
-
-/* PyObjectCallOneArg */
-  #if CYTHON_COMPILING_IN_CPYTHON
-static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) {
-    PyObject *result;
-    PyObject *args = PyTuple_New(1);
-    if (unlikely(!args)) return NULL;
-    Py_INCREF(arg);
-    PyTuple_SET_ITEM(args, 0, arg);
-    result = __Pyx_PyObject_Call(func, args, NULL);
-    Py_DECREF(args);
-    return result;
-}
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
-#if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(func)) {
-        return __Pyx_PyFunction_FastCall(func, &arg, 1);
-    }
-#endif
-#ifdef __Pyx_CyFunction_USED
-    if (likely(PyCFunction_Check(func) || PyObject_TypeCheck(func, __pyx_CyFunctionType))) {
-#else
-    if (likely(PyCFunction_Check(func))) {
-#endif
-        if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) {
-            return __Pyx_PyObject_CallMethO(func, arg);
-#if CYTHON_FAST_PYCCALL
-        } else if (PyCFunction_GET_FLAGS(func) & METH_FASTCALL) {
-            return __Pyx_PyCFunction_FastCall(func, &arg, 1);
-#endif
-        }
-    }
-    return __Pyx__PyObject_CallOneArg(func, arg);
-}
-#else
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
-    PyObject *result;
-    PyObject *args = PyTuple_Pack(1, arg);
-    if (unlikely(!args)) return NULL;
-    result = __Pyx_PyObject_Call(func, args, NULL);
-    Py_DECREF(args);
-    return result;
-}
-#endif
-
 /* PyErrFetchRestore */
-    #if CYTHON_FAST_THREAD_STATE
+#if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) {
     PyObject *tmp_type, *tmp_value, *tmp_tb;
     tmp_type = tstate->curexc_type;
@@ -4952,7 +7024,7 @@ static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject
 #endif
 
 /* RaiseException */
-    #if PY_MAJOR_VERSION < 3
+#if PY_MAJOR_VERSION < 3
 static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb,
                         CYTHON_UNUSED PyObject *cause) {
     __Pyx_PyThreadState_declare
@@ -5083,45 +7155,143 @@ static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject
             fixed_cause = cause;
             Py_INCREF(fixed_cause);
         } else {
-            PyErr_SetString(PyExc_TypeError,
-                            "exception causes must derive from "
-                            "BaseException");
-            goto bad;
+            PyErr_SetString(PyExc_TypeError,
+                            "exception causes must derive from "
+                            "BaseException");
+            goto bad;
+        }
+        PyException_SetCause(value, fixed_cause);
+    }
+    PyErr_SetObject(type, value);
+    if (tb) {
+#if CYTHON_COMPILING_IN_PYPY
+        PyObject *tmp_type, *tmp_value, *tmp_tb;
+        PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb);
+        Py_INCREF(tb);
+        PyErr_Restore(tmp_type, tmp_value, tb);
+        Py_XDECREF(tmp_tb);
+#else
+        PyThreadState *tstate = PyThreadState_GET();
+        PyObject* tmp_tb = tstate->curexc_traceback;
+        if (tb != tmp_tb) {
+            Py_INCREF(tb);
+            tstate->curexc_traceback = tb;
+            Py_XDECREF(tmp_tb);
+        }
+#endif
+    }
+bad:
+    Py_XDECREF(owned_instance);
+    return;
+}
+#endif
+
+/* SetItemInt */
+  static CYTHON_INLINE int __Pyx_SetItemInt_Generic(PyObject *o, PyObject *j, PyObject *v) {
+    int r;
+    if (!j) return -1;
+    r = PyObject_SetItem(o, j, v);
+    Py_DECREF(j);
+    return r;
+}
+static CYTHON_INLINE int __Pyx_SetItemInt_Fast(PyObject *o, Py_ssize_t i, PyObject *v, int is_list,
+                                               CYTHON_NCP_UNUSED int wraparound, CYTHON_NCP_UNUSED int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (is_list || PyList_CheckExact(o)) {
+        Py_ssize_t n = (!wraparound) ? i : ((likely(i >= 0)) ? i : i + PyList_GET_SIZE(o));
+        if ((!boundscheck) || likely((n >= 0) & (n < PyList_GET_SIZE(o)))) {
+            PyObject* old = PyList_GET_ITEM(o, n);
+            Py_INCREF(v);
+            PyList_SET_ITEM(o, n, v);
+            Py_DECREF(old);
+            return 1;
+        }
+    } else {
+        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
+        if (likely(m && m->sq_ass_item)) {
+            if (wraparound && unlikely(i < 0) && likely(m->sq_length)) {
+                Py_ssize_t l = m->sq_length(o);
+                if (likely(l >= 0)) {
+                    i += l;
+                } else {
+                    if (!PyErr_ExceptionMatches(PyExc_OverflowError))
+                        return -1;
+                    PyErr_Clear();
+                }
+            }
+            return m->sq_ass_item(o, i, v);
+        }
+    }
+#else
+#if CYTHON_COMPILING_IN_PYPY
+    if (is_list || (PySequence_Check(o) && !PyDict_Check(o))) {
+#else
+    if (is_list || PySequence_Check(o)) {
+#endif
+        return PySequence_SetItem(o, i, v);
+    }
+#endif
+    return __Pyx_SetItemInt_Generic(o, PyInt_FromSsize_t(i), v);
+}
+
+/* IterFinish */
+    static CYTHON_INLINE int __Pyx_IterFinish(void) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    PyThreadState *tstate = PyThreadState_GET();
+    PyObject* exc_type = tstate->curexc_type;
+    if (unlikely(exc_type)) {
+        if (likely(exc_type == PyExc_StopIteration) || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)) {
+            PyObject *exc_value, *exc_tb;
+            exc_value = tstate->curexc_value;
+            exc_tb = tstate->curexc_traceback;
+            tstate->curexc_type = 0;
+            tstate->curexc_value = 0;
+            tstate->curexc_traceback = 0;
+            Py_DECREF(exc_type);
+            Py_XDECREF(exc_value);
+            Py_XDECREF(exc_tb);
+            return 0;
+        } else {
+            return -1;
         }
-        PyException_SetCause(value, fixed_cause);
     }
-    PyErr_SetObject(type, value);
-    if (tb) {
-#if CYTHON_COMPILING_IN_PYPY
-        PyObject *tmp_type, *tmp_value, *tmp_tb;
-        PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb);
-        Py_INCREF(tb);
-        PyErr_Restore(tmp_type, tmp_value, tb);
-        Py_XDECREF(tmp_tb);
+    return 0;
 #else
-        PyThreadState *tstate = PyThreadState_GET();
-        PyObject* tmp_tb = tstate->curexc_traceback;
-        if (tb != tmp_tb) {
-            Py_INCREF(tb);
-            tstate->curexc_traceback = tb;
-            Py_XDECREF(tmp_tb);
+    if (unlikely(PyErr_Occurred())) {
+        if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) {
+            PyErr_Clear();
+            return 0;
+        } else {
+            return -1;
         }
+    }
+    return 0;
 #endif
+}
+
+/* PyObjectCallMethO */
+    #if CYTHON_COMPILING_IN_CPYTHON
+static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) {
+    PyObject *self, *result;
+    PyCFunction cfunc;
+    cfunc = PyCFunction_GET_FUNCTION(func);
+    self = PyCFunction_GET_SELF(func);
+    if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object")))
+        return NULL;
+    result = cfunc(self, arg);
+    Py_LeaveRecursiveCall();
+    if (unlikely(!result) && unlikely(!PyErr_Occurred())) {
+        PyErr_SetString(
+            PyExc_SystemError,
+            "NULL result without error in PyObject_Call");
     }
-bad:
-    Py_XDECREF(owned_instance);
-    return;
+    return result;
 }
 #endif
 
 /* PyObjectCallNoArg */
-      #if CYTHON_COMPILING_IN_CPYTHON
+    #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func) {
-#if CYTHON_FAST_PYCALL
-    if (PyFunction_Check(func)) {
-        return __Pyx_PyFunction_FastCall(func, NULL, 0);
-    }
-#endif
 #ifdef __Pyx_CyFunction_USED
     if (likely(PyCFunction_Check(func) || PyObject_TypeCheck(func, __pyx_CyFunctionType))) {
 #else
@@ -5135,127 +7305,332 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func) {
 }
 #endif
 
-/* IterNext */
-        static CYTHON_INLINE PyObject *__Pyx_PyIter_Next2(PyObject* iterator, PyObject* defval) {
-    PyObject* next;
-    iternextfunc iternext = Py_TYPE(iterator)->tp_iternext;
-#if CYTHON_USE_TYPE_SLOTS
-    if (unlikely(!iternext)) {
+/* PyObjectCallOneArg */
+      #if CYTHON_COMPILING_IN_CPYTHON
+static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) {
+    PyObject *result;
+    PyObject *args = PyTuple_New(1);
+    if (unlikely(!args)) return NULL;
+    Py_INCREF(arg);
+    PyTuple_SET_ITEM(args, 0, arg);
+    result = __Pyx_PyObject_Call(func, args, NULL);
+    Py_DECREF(args);
+    return result;
+}
+static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
+#ifdef __Pyx_CyFunction_USED
+    if (likely(PyCFunction_Check(func) || PyObject_TypeCheck(func, __pyx_CyFunctionType))) {
 #else
-    if (unlikely(!iternext) || unlikely(!PyIter_Check(iterator))) {
+    if (likely(PyCFunction_Check(func))) {
 #endif
-        PyErr_Format(PyExc_TypeError,
-            "%.200s object is not an iterator", Py_TYPE(iterator)->tp_name);
-        return NULL;
+        if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) {
+            return __Pyx_PyObject_CallMethO(func, arg);
+        }
     }
-    next = iternext(iterator);
-    if (likely(next))
-        return next;
-#if CYTHON_USE_TYPE_SLOTS
-#if PY_VERSION_HEX >= 0x02070000
-    if (unlikely(iternext == &_PyObject_NextNotImplemented))
-        return NULL;
-#endif
+    return __Pyx__PyObject_CallOneArg(func, arg);
+}
+#else
+static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
+    PyObject *result;
+    PyObject *args = PyTuple_Pack(1, arg);
+    if (unlikely(!args)) return NULL;
+    result = __Pyx_PyObject_Call(func, args, NULL);
+    Py_DECREF(args);
+    return result;
+}
 #endif
-    if (defval) {
-        PyObject* exc_type = PyErr_Occurred();
-        if (exc_type) {
-            if (unlikely(exc_type != PyExc_StopIteration) &&
-                    !PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))
-                return NULL;
-            PyErr_Clear();
+
+/* PyObjectCallMethod0 */
+        static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name) {
+    PyObject *method, *result = NULL;
+    method = __Pyx_PyObject_GetAttrStr(obj, method_name);
+    if (unlikely(!method)) goto bad;
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (likely(PyMethod_Check(method))) {
+        PyObject *self = PyMethod_GET_SELF(method);
+        if (likely(self)) {
+            PyObject *function = PyMethod_GET_FUNCTION(method);
+            result = __Pyx_PyObject_CallOneArg(function, self);
+            Py_DECREF(method);
+            return result;
         }
-        Py_INCREF(defval);
-        return defval;
     }
-    if (!PyErr_Occurred())
-        PyErr_SetNone(PyExc_StopIteration);
-    return NULL;
+#endif
+    result = __Pyx_PyObject_CallNoArg(method);
+    Py_DECREF(method);
+bad:
+    return result;
 }
 
-/* GetItemInt */
-          static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) {
-    PyObject *r;
-    if (!j) return NULL;
-    r = PyObject_GetItem(o, j);
-    Py_DECREF(j);
-    return r;
+/* RaiseNeedMoreValuesToUnpack */
+        static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
+    PyErr_Format(PyExc_ValueError,
+                 "need more than %" CYTHON_FORMAT_SSIZE_T "d value%.1s to unpack",
+                 index, (index == 1) ? "" : "s");
 }
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
-                                                              CYTHON_NCP_UNUSED int wraparound,
-                                                              CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-    if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
-    if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
-        PyObject *r = PyList_GET_ITEM(o, i);
-        Py_INCREF(r);
-        return r;
+
+/* RaiseTooManyValuesToUnpack */
+        static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected) {
+    PyErr_Format(PyExc_ValueError,
+                 "too many values to unpack (expected %" CYTHON_FORMAT_SSIZE_T "d)", expected);
+}
+
+/* UnpackItemEndCheck */
+        static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) {
+    if (unlikely(retval)) {
+        Py_DECREF(retval);
+        __Pyx_RaiseTooManyValuesError(expected);
+        return -1;
+    } else {
+        return __Pyx_IterFinish();
     }
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
+    return 0;
+}
+
+/* RaiseNoneIterError */
+        static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void) {
+    PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable");
+}
+
+/* UnpackTupleError */
+        static void __Pyx_UnpackTupleError(PyObject *t, Py_ssize_t index) {
+    if (t == Py_None) {
+      __Pyx_RaiseNoneNotIterableError();
+    } else if (PyTuple_GET_SIZE(t) < index) {
+      __Pyx_RaiseNeedMoreValuesError(PyTuple_GET_SIZE(t));
+    } else {
+      __Pyx_RaiseTooManyValuesError(index);
+    }
+}
+
+/* UnpackTuple2 */
+        static CYTHON_INLINE int __Pyx_unpack_tuple2(PyObject* tuple, PyObject** pvalue1, PyObject** pvalue2,
+                                             int is_tuple, int has_known_size, int decref_tuple) {
+    Py_ssize_t index;
+    PyObject *value1 = NULL, *value2 = NULL, *iter = NULL;
+    if (!is_tuple && unlikely(!PyTuple_Check(tuple))) {
+        iternextfunc iternext;
+        iter = PyObject_GetIter(tuple);
+        if (unlikely(!iter)) goto bad;
+        if (decref_tuple) { Py_DECREF(tuple); tuple = NULL; }
+        iternext = Py_TYPE(iter)->tp_iternext;
+        value1 = iternext(iter); if (unlikely(!value1)) { index = 0; goto unpacking_failed; }
+        value2 = iternext(iter); if (unlikely(!value2)) { index = 1; goto unpacking_failed; }
+        if (!has_known_size && unlikely(__Pyx_IternextUnpackEndCheck(iternext(iter), 2))) goto bad;
+        Py_DECREF(iter);
+    } else {
+        if (!has_known_size && unlikely(PyTuple_GET_SIZE(tuple) != 2)) {
+            __Pyx_UnpackTupleError(tuple, 2);
+            goto bad;
+        }
+#if CYTHON_COMPILING_IN_PYPY
+        value1 = PySequence_ITEM(tuple, 0);
+        if (unlikely(!value1)) goto bad;
+        value2 = PySequence_ITEM(tuple, 1);
+        if (unlikely(!value2)) goto bad;
 #else
-    return PySequence_GetItem(o, i);
+        value1 = PyTuple_GET_ITEM(tuple, 0);
+        value2 = PyTuple_GET_ITEM(tuple, 1);
+        Py_INCREF(value1);
+        Py_INCREF(value2);
 #endif
+        if (decref_tuple) { Py_DECREF(tuple); }
+    }
+    *pvalue1 = value1;
+    *pvalue2 = value2;
+    return 0;
+unpacking_failed:
+    if (!has_known_size && __Pyx_IterFinish() == 0)
+        __Pyx_RaiseNeedMoreValuesError(index);
+bad:
+    Py_XDECREF(iter);
+    Py_XDECREF(value1);
+    Py_XDECREF(value2);
+    if (decref_tuple) { Py_XDECREF(tuple); }
+    return -1;
 }
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
-                                                              CYTHON_NCP_UNUSED int wraparound,
-                                                              CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-    if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
-    if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
-        PyObject *r = PyTuple_GET_ITEM(o, i);
-        Py_INCREF(r);
-        return r;
+
+/* dict_iter */
+        static CYTHON_INLINE PyObject* __Pyx_dict_iterator(PyObject* iterable, int is_dict, PyObject* method_name,
+                                                   Py_ssize_t* p_orig_length, int* p_source_is_dict) {
+    is_dict = is_dict || likely(PyDict_CheckExact(iterable));
+    *p_source_is_dict = is_dict;
+#if !CYTHON_COMPILING_IN_PYPY
+    if (is_dict) {
+        *p_orig_length = PyDict_Size(iterable);
+        Py_INCREF(iterable);
+        return iterable;
     }
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
-#else
-    return PySequence_GetItem(o, i);
 #endif
-}
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list,
-                                                     CYTHON_NCP_UNUSED int wraparound,
-                                                     CYTHON_NCP_UNUSED int boundscheck) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS
-    if (is_list || PyList_CheckExact(o)) {
-        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
-        if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
-            PyObject *r = PyList_GET_ITEM(o, n);
-            Py_INCREF(r);
-            return r;
+    *p_orig_length = 0;
+    if (method_name) {
+        PyObject* iter;
+        iterable = __Pyx_PyObject_CallMethod0(iterable, method_name);
+        if (!iterable)
+            return NULL;
+#if !CYTHON_COMPILING_IN_PYPY
+        if (PyTuple_CheckExact(iterable) || PyList_CheckExact(iterable))
+            return iterable;
+#endif
+        iter = PyObject_GetIter(iterable);
+        Py_DECREF(iterable);
+        return iter;
+    }
+    return PyObject_GetIter(iterable);
+}
+static CYTHON_INLINE int __Pyx_dict_iter_next(
+        PyObject* iter_obj, CYTHON_NCP_UNUSED Py_ssize_t orig_length, CYTHON_NCP_UNUSED Py_ssize_t* ppos,
+        PyObject** pkey, PyObject** pvalue, PyObject** pitem, int source_is_dict) {
+    PyObject* next_item;
+#if !CYTHON_COMPILING_IN_PYPY
+    if (source_is_dict) {
+        PyObject *key, *value;
+        if (unlikely(orig_length != PyDict_Size(iter_obj))) {
+            PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration");
+            return -1;
         }
-    }
-    else if (PyTuple_CheckExact(o)) {
-        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o);
-        if ((!boundscheck) || likely((n >= 0) & (n < PyTuple_GET_SIZE(o)))) {
-            PyObject *r = PyTuple_GET_ITEM(o, n);
-            Py_INCREF(r);
-            return r;
+        if (unlikely(!PyDict_Next(iter_obj, ppos, &key, &value))) {
+            return 0;
         }
-    } else {
-        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
-        if (likely(m && m->sq_item)) {
-            if (wraparound && unlikely(i < 0) && likely(m->sq_length)) {
-                Py_ssize_t l = m->sq_length(o);
-                if (likely(l >= 0)) {
-                    i += l;
-                } else {
-                    if (!PyErr_ExceptionMatches(PyExc_OverflowError))
-                        return NULL;
-                    PyErr_Clear();
-                }
+        if (pitem) {
+            PyObject* tuple = PyTuple_New(2);
+            if (unlikely(!tuple)) {
+                return -1;
             }
-            return m->sq_item(o, i);
+            Py_INCREF(key);
+            Py_INCREF(value);
+            PyTuple_SET_ITEM(tuple, 0, key);
+            PyTuple_SET_ITEM(tuple, 1, value);
+            *pitem = tuple;
+        } else {
+            if (pkey) {
+                Py_INCREF(key);
+                *pkey = key;
+            }
+            if (pvalue) {
+                Py_INCREF(value);
+                *pvalue = value;
+            }
+        }
+        return 1;
+    } else if (PyTuple_CheckExact(iter_obj)) {
+        Py_ssize_t pos = *ppos;
+        if (unlikely(pos >= PyTuple_GET_SIZE(iter_obj))) return 0;
+        *ppos = pos + 1;
+        next_item = PyTuple_GET_ITEM(iter_obj, pos);
+        Py_INCREF(next_item);
+    } else if (PyList_CheckExact(iter_obj)) {
+        Py_ssize_t pos = *ppos;
+        if (unlikely(pos >= PyList_GET_SIZE(iter_obj))) return 0;
+        *ppos = pos + 1;
+        next_item = PyList_GET_ITEM(iter_obj, pos);
+        Py_INCREF(next_item);
+    } else
+#endif
+    {
+        next_item = PyIter_Next(iter_obj);
+        if (unlikely(!next_item)) {
+            return __Pyx_IterFinish();
         }
     }
+    if (pitem) {
+        *pitem = next_item;
+    } else if (pkey && pvalue) {
+        if (__Pyx_unpack_tuple2(next_item, pkey, pvalue, source_is_dict, source_is_dict, 1))
+            return -1;
+    } else if (pkey) {
+        *pkey = next_item;
+    } else {
+        *pvalue = next_item;
+    }
+    return 1;
+}
+
+/* ArgTypeTest */
+        static void __Pyx_RaiseArgumentTypeInvalid(const char* name, PyObject *obj, PyTypeObject *type) {
+    PyErr_Format(PyExc_TypeError,
+        "Argument '%.200s' has incorrect type (expected %.200s, got %.200s)",
+        name, type->tp_name, Py_TYPE(obj)->tp_name);
+}
+static CYTHON_INLINE int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed,
+    const char *name, int exact)
+{
+    if (unlikely(!type)) {
+        PyErr_SetString(PyExc_SystemError, "Missing type object");
+        return 0;
+    }
+    if (none_allowed && obj == Py_None) return 1;
+    else if (exact) {
+        if (likely(Py_TYPE(obj) == type)) return 1;
+        #if PY_MAJOR_VERSION == 2
+        else if ((type == &PyBaseString_Type) && likely(__Pyx_PyBaseString_CheckExact(obj))) return 1;
+        #endif
+    }
+    else {
+        if (likely(PyObject_TypeCheck(obj, type))) return 1;
+    }
+    __Pyx_RaiseArgumentTypeInvalid(name, obj, type);
+    return 0;
+}
+
+/* GetModuleGlobalName */
+        static CYTHON_INLINE PyObject *__Pyx_GetModuleGlobalName(PyObject *name) {
+    PyObject *result;
+#if CYTHON_COMPILING_IN_CPYTHON
+    result = PyDict_GetItem(__pyx_d, name);
+    if (likely(result)) {
+        Py_INCREF(result);
+    } else {
+#else
+    result = PyObject_GetItem(__pyx_d, name);
+    if (!result) {
+        PyErr_Clear();
+#endif
+        result = __Pyx_GetBuiltinName(name);
+    }
+    return result;
+}
+
+/* IterNext */
+          static CYTHON_INLINE PyObject *__Pyx_PyIter_Next2(PyObject* iterator, PyObject* defval) {
+    PyObject* next;
+    iternextfunc iternext = Py_TYPE(iterator)->tp_iternext;
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (unlikely(!iternext)) {
 #else
-    if (is_list || PySequence_Check(o)) {
-        return PySequence_GetItem(o, i);
+    if (unlikely(!iternext) || unlikely(!PyIter_Check(iterator))) {
+#endif
+        PyErr_Format(PyExc_TypeError,
+            "%.200s object is not an iterator", Py_TYPE(iterator)->tp_name);
+        return NULL;
     }
+    next = iternext(iterator);
+    if (likely(next))
+        return next;
+#if CYTHON_COMPILING_IN_CPYTHON
+#if PY_VERSION_HEX >= 0x02070000
+    if (unlikely(iternext == &_PyObject_NextNotImplemented))
+        return NULL;
 #endif
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
+#endif
+    if (defval) {
+        PyObject* exc_type = PyErr_Occurred();
+        if (exc_type) {
+            if (unlikely(exc_type != PyExc_StopIteration) &&
+                    !PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))
+                return NULL;
+            PyErr_Clear();
+        }
+        Py_INCREF(defval);
+        return defval;
+    }
+    if (!PyErr_Occurred())
+        PyErr_SetNone(PyExc_StopIteration);
+    return NULL;
 }
 
 /* BytesEquals */
-          static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2, int equals) {
+            static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2, int equals) {
 #if CYTHON_COMPILING_IN_PYPY
     return PyObject_RichCompareBool(s1, s2, equals);
 #else
@@ -5293,7 +7668,7 @@ static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
 }
 
 /* UnicodeEquals */
-          static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int equals) {
+            static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int equals) {
 #if CYTHON_COMPILING_IN_PYPY
     return PyObject_RichCompareBool(s1, s2, equals);
 #else
@@ -5377,7 +7752,7 @@ return_ne:
 }
 
 /* bytes_tailmatch */
-          static int __Pyx_PyBytes_SingleTailmatch(PyObject* self, PyObject* arg,
+            static int __Pyx_PyBytes_SingleTailmatch(PyObject* self, PyObject* arg,
                                          Py_ssize_t start, Py_ssize_t end, int direction) {
     const char* self_ptr = PyBytes_AS_STRING(self);
     Py_ssize_t self_len = PyBytes_GET_SIZE(self);
@@ -5429,7 +7804,7 @@ static int __Pyx_PyBytes_Tailmatch(PyObject* self, PyObject* substr,
         Py_ssize_t i, count = PyTuple_GET_SIZE(substr);
         for (i = 0; i < count; i++) {
             int result;
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
             result = __Pyx_PyBytes_SingleTailmatch(self, PyTuple_GET_ITEM(substr, i),
                                                    start, end, direction);
 #else
@@ -5448,13 +7823,13 @@ static int __Pyx_PyBytes_Tailmatch(PyObject* self, PyObject* substr,
 }
 
 /* unicode_tailmatch */
-          static int __Pyx_PyUnicode_Tailmatch(PyObject* s, PyObject* substr,
+            static int __Pyx_PyUnicode_Tailmatch(PyObject* s, PyObject* substr,
                                      Py_ssize_t start, Py_ssize_t end, int direction) {
     if (unlikely(PyTuple_Check(substr))) {
         Py_ssize_t i, count = PyTuple_GET_SIZE(substr);
         for (i = 0; i < count; i++) {
             Py_ssize_t result;
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+#if CYTHON_COMPILING_IN_CPYTHON
             result = PyUnicode_Tailmatch(s, PyTuple_GET_ITEM(substr, i),
                                          start, end, direction);
 #else
@@ -5473,7 +7848,7 @@ static int __Pyx_PyBytes_Tailmatch(PyObject* self, PyObject* substr,
 }
 
 /* str_tailmatch */
-          static CYTHON_INLINE int __Pyx_PyStr_Tailmatch(PyObject* self, PyObject* arg, Py_ssize_t start,
+            static CYTHON_INLINE int __Pyx_PyStr_Tailmatch(PyObject* self, PyObject* arg, Py_ssize_t start,
                                                Py_ssize_t end, int direction)
 {
     if (PY_MAJOR_VERSION < 3)
@@ -5483,19 +7858,19 @@ static int __Pyx_PyBytes_Tailmatch(PyObject* self, PyObject* substr,
 }
 
 /* None */
-          static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname) {
+            static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname) {
     PyErr_Format(PyExc_UnboundLocalError, "local variable '%s' referenced before assignment", varname);
 }
 
 /* None */
-          static CYTHON_INLINE long __Pyx_mod_long(long a, long b) {
+            static CYTHON_INLINE long __Pyx_mod_long(long a, long b) {
     long r = a % b;
     r += ((r != 0) & ((r ^ b) < 0)) * b;
     return r;
 }
 
 /* Import */
-          static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) {
+            static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) {
     PyObject *empty_list = 0;
     PyObject *module = 0;
     PyObject *global_dict = 0;
@@ -5569,7 +7944,7 @@ bad:
 }
 
 /* ImportFrom */
-          static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name) {
+            static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name) {
     PyObject* value = __Pyx_PyObject_GetAttrStr(module, name);
     if (unlikely(!value) && PyErr_ExceptionMatches(PyExc_AttributeError)) {
         PyErr_Format(PyExc_ImportError,
@@ -5582,47 +7957,8 @@ bad:
     return value;
 }
 
-/* CalculateMetaclass */
-          static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases) {
-    Py_ssize_t i, nbases = PyTuple_GET_SIZE(bases);
-    for (i=0; i < nbases; i++) {
-        PyTypeObject *tmptype;
-        PyObject *tmp = PyTuple_GET_ITEM(bases, i);
-        tmptype = Py_TYPE(tmp);
-#if PY_MAJOR_VERSION < 3
-        if (tmptype == &PyClass_Type)
-            continue;
-#endif
-        if (!metaclass) {
-            metaclass = tmptype;
-            continue;
-        }
-        if (PyType_IsSubtype(metaclass, tmptype))
-            continue;
-        if (PyType_IsSubtype(tmptype, metaclass)) {
-            metaclass = tmptype;
-            continue;
-        }
-        PyErr_SetString(PyExc_TypeError,
-                        "metaclass conflict: "
-                        "the metaclass of a derived class "
-                        "must be a (non-strict) subclass "
-                        "of the metaclasses of all its bases");
-        return NULL;
-    }
-    if (!metaclass) {
-#if PY_MAJOR_VERSION < 3
-        metaclass = &PyClass_Type;
-#else
-        metaclass = &PyType_Type;
-#endif
-    }
-    Py_INCREF((PyObject*) metaclass);
-    return (PyObject*) metaclass;
-}
-
 /* FetchCommonType */
-          static PyTypeObject* __Pyx_FetchCommonType(PyTypeObject* type) {
+            static PyTypeObject* __Pyx_FetchCommonType(PyTypeObject* type) {
     PyObject* fake_module;
     PyTypeObject* cached_type = NULL;
     fake_module = PyImport_AddModule((char*) "_cython_" CYTHON_ABI);
@@ -5661,7 +7997,7 @@ bad:
 }
 
 /* CythonFunction */
-          static PyObject *
+            static PyObject *
 __Pyx_CyFunction_get_doc(__pyx_CyFunctionObject *op, CYTHON_UNUSED void *closure)
 {
     if (unlikely(op->func_doc == NULL)) {
@@ -5818,7 +8154,7 @@ __Pyx_CyFunction_init_defaults(__pyx_CyFunctionObject *op) {
     PyObject *res = op->defaults_getter((PyObject *) op);
     if (unlikely(!res))
         return -1;
-    #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
+    #if CYTHON_COMPILING_IN_CPYTHON
     op->defaults_tuple = PyTuple_GET_ITEM(res, 0);
     Py_INCREF(op->defaults_tuple);
     op->defaults_kwdict = PyTuple_GET_ITEM(res, 1);
@@ -6073,90 +8409,435 @@ __Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
     return PyUnicode_FromFormat("<cyfunction %U at %p>",
                                 op->func_qualname, (void *)op);
 #else
-    return PyString_FromFormat("<cyfunction %s at %p>",
-                               PyString_AsString(op->func_qualname), (void *)op);
+    return PyString_FromFormat("<cyfunction %s at %p>",
+                               PyString_AsString(op->func_qualname), (void *)op);
+#endif
+}
+#if CYTHON_COMPILING_IN_PYPY
+static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
+    PyCFunctionObject* f = (PyCFunctionObject*)func;
+    PyCFunction meth = f->m_ml->ml_meth;
+    PyObject *self = f->m_self;
+    Py_ssize_t size;
+    switch (f->m_ml->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
+    case METH_VARARGS:
+        if (likely(kw == NULL || PyDict_Size(kw) == 0))
+            return (*meth)(self, arg);
+        break;
+    case METH_VARARGS | METH_KEYWORDS:
+        return (*(PyCFunctionWithKeywords)meth)(self, arg, kw);
+    case METH_NOARGS:
+        if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
+            size = PyTuple_GET_SIZE(arg);
+            if (likely(size == 0))
+                return (*meth)(self, NULL);
+            PyErr_Format(PyExc_TypeError,
+                "%.200s() takes no arguments (%" CYTHON_FORMAT_SSIZE_T "d given)",
+                f->m_ml->ml_name, size);
+            return NULL;
+        }
+        break;
+    case METH_O:
+        if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
+            size = PyTuple_GET_SIZE(arg);
+            if (likely(size == 1)) {
+                PyObject *result, *arg0 = PySequence_ITEM(arg, 0);
+                if (unlikely(!arg0)) return NULL;
+                result = (*meth)(self, arg0);
+                Py_DECREF(arg0);
+                return result;
+            }
+            PyErr_Format(PyExc_TypeError,
+                "%.200s() takes exactly one argument (%" CYTHON_FORMAT_SSIZE_T "d given)",
+                f->m_ml->ml_name, size);
+            return NULL;
+        }
+        break;
+    default:
+        PyErr_SetString(PyExc_SystemError, "Bad call flags in "
+                        "__Pyx_CyFunction_Call. METH_OLDARGS is no "
+                        "longer supported!");
+        return NULL;
+    }
+    PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
+                 f->m_ml->ml_name);
+    return NULL;
+}
+#else
+static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
+	return PyCFunction_Call(func, arg, kw);
+}
+#endif
+static PyTypeObject __pyx_CyFunctionType_type = {
+    PyVarObject_HEAD_INIT(0, 0)
+    "cython_function_or_method",
+    sizeof(__pyx_CyFunctionObject),
+    0,
+    (destructor) __Pyx_CyFunction_dealloc,
+    0,
+    0,
+    0,
+#if PY_MAJOR_VERSION < 3
+    0,
+#else
+    0,
+#endif
+    (reprfunc) __Pyx_CyFunction_repr,
+    0,
+    0,
+    0,
+    0,
+    __Pyx_CyFunction_Call,
+    0,
+    0,
+    0,
+    0,
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    0,
+    (traverseproc) __Pyx_CyFunction_traverse,
+    (inquiry) __Pyx_CyFunction_clear,
+    0,
+#if PY_VERSION_HEX < 0x030500A0
+    offsetof(__pyx_CyFunctionObject, func_weakreflist),
+#else
+    offsetof(PyCFunctionObject, m_weakreflist),
+#endif
+    0,
+    0,
+    __pyx_CyFunction_methods,
+    __pyx_CyFunction_members,
+    __pyx_CyFunction_getsets,
+    0,
+    0,
+    __Pyx_CyFunction_descr_get,
+    0,
+    offsetof(__pyx_CyFunctionObject, func_dict),
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+#if PY_VERSION_HEX >= 0x030400a1
+    0,
+#endif
+};
+static int __pyx_CyFunction_init(void) {
+#if !CYTHON_COMPILING_IN_PYPY
+    __pyx_CyFunctionType_type.tp_call = PyCFunction_Call;
+#endif
+    __pyx_CyFunctionType = __Pyx_FetchCommonType(&__pyx_CyFunctionType_type);
+    if (__pyx_CyFunctionType == NULL) {
+        return -1;
+    }
+    return 0;
+}
+static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *func, size_t size, int pyobjects) {
+    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
+    m->defaults = PyObject_Malloc(size);
+    if (!m->defaults)
+        return PyErr_NoMemory();
+    memset(m->defaults, 0, size);
+    m->defaults_pyobjects = pyobjects;
+    return m->defaults;
+}
+static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *func, PyObject *tuple) {
+    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
+    m->defaults_tuple = tuple;
+    Py_INCREF(tuple);
+}
+static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *func, PyObject *dict) {
+    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
+    m->defaults_kwdict = dict;
+    Py_INCREF(dict);
+}
+static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *func, PyObject *dict) {
+    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
+    m->func_annotations = dict;
+    Py_INCREF(dict);
+}
+
+/* FusedFunction */
+                static PyObject *
+__pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags,
+                        PyObject *qualname, PyObject *self,
+                        PyObject *module, PyObject *globals,
+                        PyObject *code)
+{
+    __pyx_FusedFunctionObject *fusedfunc =
+        (__pyx_FusedFunctionObject *) __Pyx_CyFunction_New(type, ml, flags, qualname,
+                                                           self, module, globals, code);
+    if (!fusedfunc)
+        return NULL;
+    fusedfunc->__signatures__ = NULL;
+    fusedfunc->type = NULL;
+    fusedfunc->self = NULL;
+    return (PyObject *) fusedfunc;
+}
+static void __pyx_FusedFunction_dealloc(__pyx_FusedFunctionObject *self) {
+    __pyx_FusedFunction_clear(self);
+    __pyx_FusedFunctionType->tp_free((PyObject *) self);
+}
+static int
+__pyx_FusedFunction_traverse(__pyx_FusedFunctionObject *self,
+                             visitproc visit,
+                             void *arg)
+{
+    Py_VISIT(self->self);
+    Py_VISIT(self->type);
+    Py_VISIT(self->__signatures__);
+    return __Pyx_CyFunction_traverse((__pyx_CyFunctionObject *) self, visit, arg);
+}
+static int
+__pyx_FusedFunction_clear(__pyx_FusedFunctionObject *self)
+{
+    Py_CLEAR(self->self);
+    Py_CLEAR(self->type);
+    Py_CLEAR(self->__signatures__);
+    return __Pyx_CyFunction_clear((__pyx_CyFunctionObject *) self);
+}
+static PyObject *
+__pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
+{
+    __pyx_FusedFunctionObject *func, *meth;
+    func = (__pyx_FusedFunctionObject *) self;
+    if (func->self || func->func.flags & __Pyx_CYFUNCTION_STATICMETHOD) {
+        Py_INCREF(self);
+        return self;
+    }
+    if (obj == Py_None)
+        obj = NULL;
+    meth = (__pyx_FusedFunctionObject *) __pyx_FusedFunction_NewEx(
+                    ((PyCFunctionObject *) func)->m_ml,
+                    ((__pyx_CyFunctionObject *) func)->flags,
+                    ((__pyx_CyFunctionObject *) func)->func_qualname,
+                    ((__pyx_CyFunctionObject *) func)->func_closure,
+                    ((PyCFunctionObject *) func)->m_module,
+                    ((__pyx_CyFunctionObject *) func)->func_globals,
+                    ((__pyx_CyFunctionObject *) func)->func_code);
+    if (!meth)
+        return NULL;
+    Py_XINCREF(func->func.func_classobj);
+    meth->func.func_classobj = func->func.func_classobj;
+    Py_XINCREF(func->__signatures__);
+    meth->__signatures__ = func->__signatures__;
+    Py_XINCREF(type);
+    meth->type = type;
+    Py_XINCREF(func->func.defaults_tuple);
+    meth->func.defaults_tuple = func->func.defaults_tuple;
+    if (func->func.flags & __Pyx_CYFUNCTION_CLASSMETHOD)
+        obj = type;
+    Py_XINCREF(obj);
+    meth->self = obj;
+    return (PyObject *) meth;
+}
+static PyObject *
+_obj_to_str(PyObject *obj)
+{
+    if (PyType_Check(obj))
+        return PyObject_GetAttr(obj, __pyx_n_s_name_2);
+    else
+        return PyObject_Str(obj);
+}
+static PyObject *
+__pyx_FusedFunction_getitem(__pyx_FusedFunctionObject *self, PyObject *idx)
+{
+    PyObject *signature = NULL;
+    PyObject *unbound_result_func;
+    PyObject *result_func = NULL;
+    if (self->__signatures__ == NULL) {
+        PyErr_SetString(PyExc_TypeError, "Function is not fused");
+        return NULL;
+    }
+    if (PyTuple_Check(idx)) {
+        PyObject *list = PyList_New(0);
+        Py_ssize_t n = PyTuple_GET_SIZE(idx);
+        PyObject *string = NULL;
+        PyObject *sep = NULL;
+        int i;
+        if (!list)
+            return NULL;
+        for (i = 0; i < n; i++) {
+#if CYTHON_COMPILING_IN_CPYTHON
+            PyObject *item = PyTuple_GET_ITEM(idx, i);
+#else
+            PyObject *item = PySequence_ITEM(idx, i);
 #endif
-}
-static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, PyObject *arg, PyObject *kw) {
-    PyCFunctionObject* f = (PyCFunctionObject*)func;
-    PyCFunction meth = f->m_ml->ml_meth;
-    Py_ssize_t size;
-    switch (f->m_ml->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
-    case METH_VARARGS:
-        if (likely(kw == NULL || PyDict_Size(kw) == 0))
-            return (*meth)(self, arg);
-        break;
-    case METH_VARARGS | METH_KEYWORDS:
-        return (*(PyCFunctionWithKeywords)meth)(self, arg, kw);
-    case METH_NOARGS:
-        if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
-            size = PyTuple_GET_SIZE(arg);
-            if (likely(size == 0))
-                return (*meth)(self, NULL);
-            PyErr_Format(PyExc_TypeError,
-                "%.200s() takes no arguments (%" CYTHON_FORMAT_SSIZE_T "d given)",
-                f->m_ml->ml_name, size);
-            return NULL;
-        }
-        break;
-    case METH_O:
-        if (likely(kw == NULL || PyDict_Size(kw) == 0)) {
-            size = PyTuple_GET_SIZE(arg);
-            if (likely(size == 1)) {
-                PyObject *result, *arg0 = PySequence_ITEM(arg, 0);
-                if (unlikely(!arg0)) return NULL;
-                result = (*meth)(self, arg0);
-                Py_DECREF(arg0);
-                return result;
-            }
-            PyErr_Format(PyExc_TypeError,
-                "%.200s() takes exactly one argument (%" CYTHON_FORMAT_SSIZE_T "d given)",
-                f->m_ml->ml_name, size);
-            return NULL;
+            string = _obj_to_str(item);
+#if !CYTHON_COMPILING_IN_CPYTHON
+            Py_DECREF(item);
+#endif
+            if (!string || PyList_Append(list, string) < 0)
+                goto __pyx_err;
+            Py_DECREF(string);
         }
-        break;
-    default:
-        PyErr_SetString(PyExc_SystemError, "Bad call flags in "
-                        "__Pyx_CyFunction_Call. METH_OLDARGS is no "
-                        "longer supported!");
+        sep = PyUnicode_FromString("|");
+        if (sep)
+            signature = PyUnicode_Join(sep, list);
+__pyx_err:
+;
+        Py_DECREF(list);
+        Py_XDECREF(sep);
+    } else {
+        signature = _obj_to_str(idx);
+    }
+    if (!signature)
         return NULL;
+    unbound_result_func = PyObject_GetItem(self->__signatures__, signature);
+    if (unbound_result_func) {
+        if (self->self || self->type) {
+            __pyx_FusedFunctionObject *unbound = (__pyx_FusedFunctionObject *) unbound_result_func;
+            Py_CLEAR(unbound->func.func_classobj);
+            Py_XINCREF(self->func.func_classobj);
+            unbound->func.func_classobj = self->func.func_classobj;
+            result_func = __pyx_FusedFunction_descr_get(unbound_result_func,
+                                                        self->self, self->type);
+        } else {
+            result_func = unbound_result_func;
+            Py_INCREF(result_func);
+        }
     }
-    PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
-                 f->m_ml->ml_name);
-    return NULL;
-}
-static CYTHON_INLINE PyObject *__Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
-    return __Pyx_CyFunction_CallMethod(func, ((PyCFunctionObject*)func)->m_self, arg, kw);
+    Py_DECREF(signature);
+    Py_XDECREF(unbound_result_func);
+    return result_func;
 }
-static PyObject *__Pyx_CyFunction_CallAsMethod(PyObject *func, PyObject *args, PyObject *kw) {
+static PyObject *
+__pyx_FusedFunction_callfunction(PyObject *func, PyObject *args, PyObject *kw)
+{
+     __pyx_CyFunctionObject *cyfunc = (__pyx_CyFunctionObject *) func;
     PyObject *result;
-    __pyx_CyFunctionObject *cyfunc = (__pyx_CyFunctionObject *) func;
-    if ((cyfunc->flags & __Pyx_CYFUNCTION_CCLASS) && !(cyfunc->flags & __Pyx_CYFUNCTION_STATICMETHOD)) {
+    int static_specialized = (cyfunc->flags & __Pyx_CYFUNCTION_STATICMETHOD &&
+                              !((__pyx_FusedFunctionObject *) func)->__signatures__);
+    if (cyfunc->flags & __Pyx_CYFUNCTION_CCLASS && !static_specialized) {
         Py_ssize_t argc;
         PyObject *new_args;
         PyObject *self;
+        PyObject *m_self;
         argc = PyTuple_GET_SIZE(args);
         new_args = PyTuple_GetSlice(args, 1, argc);
-        if (unlikely(!new_args))
+        if (!new_args)
             return NULL;
         self = PyTuple_GetItem(args, 0);
-        if (unlikely(!self)) {
-            Py_DECREF(new_args);
+        if (!self)
             return NULL;
-        }
-        result = __Pyx_CyFunction_CallMethod(func, self, new_args, kw);
+        m_self = cyfunc->func.m_self;
+        cyfunc->func.m_self = self;
+        result = __Pyx_CyFunction_Call(func, new_args, kw);
+        cyfunc->func.m_self = m_self;
         Py_DECREF(new_args);
     } else {
         result = __Pyx_CyFunction_Call(func, args, kw);
     }
     return result;
 }
-static PyTypeObject __pyx_CyFunctionType_type = {
+static PyObject *
+__pyx_FusedFunction_call(PyObject *func, PyObject *args, PyObject *kw)
+{
+    __pyx_FusedFunctionObject *binding_func = (__pyx_FusedFunctionObject *) func;
+    Py_ssize_t argc = PyTuple_GET_SIZE(args);
+    PyObject *new_args = NULL;
+    __pyx_FusedFunctionObject *new_func = NULL;
+    PyObject *result = NULL;
+    PyObject *self = NULL;
+    int is_staticmethod = binding_func->func.flags & __Pyx_CYFUNCTION_STATICMETHOD;
+    int is_classmethod = binding_func->func.flags & __Pyx_CYFUNCTION_CLASSMETHOD;
+    if (binding_func->self) {
+        Py_ssize_t i;
+        new_args = PyTuple_New(argc + 1);
+        if (!new_args)
+            return NULL;
+        self = binding_func->self;
+#if !CYTHON_COMPILING_IN_CPYTHON
+        Py_INCREF(self);
+#endif
+        Py_INCREF(self);
+        PyTuple_SET_ITEM(new_args, 0, self);
+        for (i = 0; i < argc; i++) {
+#if CYTHON_COMPILING_IN_CPYTHON
+            PyObject *item = PyTuple_GET_ITEM(args, i);
+            Py_INCREF(item);
+#else
+            PyObject *item = PySequence_ITEM(args, i);  if (unlikely(!item)) goto bad;
+#endif
+            PyTuple_SET_ITEM(new_args, i + 1, item);
+        }
+        args = new_args;
+    } else if (binding_func->type) {
+        if (argc < 1) {
+            PyErr_SetString(PyExc_TypeError, "Need at least one argument, 0 given.");
+            return NULL;
+        }
+#if CYTHON_COMPILING_IN_CPYTHON
+        self = PyTuple_GET_ITEM(args, 0);
+#else
+        self = PySequence_ITEM(args, 0);  if (unlikely(!self)) return NULL;
+#endif
+    }
+    if (self && !is_classmethod && !is_staticmethod) {
+        int is_instance = PyObject_IsInstance(self, binding_func->type);
+        if (unlikely(!is_instance)) {
+            PyErr_Format(PyExc_TypeError,
+                         "First argument should be of type %.200s, got %.200s.",
+                         ((PyTypeObject *) binding_func->type)->tp_name,
+                         self->ob_type->tp_name);
+            goto bad;
+        } else if (unlikely(is_instance == -1)) {
+            goto bad;
+        }
+    }
+#if !CYTHON_COMPILING_IN_CPYTHON
+    Py_XDECREF(self);
+    self = NULL;
+#endif
+    if (binding_func->__signatures__) {
+        PyObject *tup = PyTuple_Pack(4, binding_func->__signatures__, args,
+                                        kw == NULL ? Py_None : kw,
+                                        binding_func->func.defaults_tuple);
+        if (!tup)
+            goto bad;
+        new_func = (__pyx_FusedFunctionObject *) __pyx_FusedFunction_callfunction(func, tup, NULL);
+        Py_DECREF(tup);
+        if (!new_func)
+            goto bad;
+        Py_XINCREF(binding_func->func.func_classobj);
+        Py_CLEAR(new_func->func.func_classobj);
+        new_func->func.func_classobj = binding_func->func.func_classobj;
+        func = (PyObject *) new_func;
+    }
+    result = __pyx_FusedFunction_callfunction(func, args, kw);
+bad:
+#if !CYTHON_COMPILING_IN_CPYTHON
+    Py_XDECREF(self);
+#endif
+    Py_XDECREF(new_args);
+    Py_XDECREF((PyObject *) new_func);
+    return result;
+}
+static PyMemberDef __pyx_FusedFunction_members[] = {
+    {(char *) "__signatures__",
+     T_OBJECT,
+     offsetof(__pyx_FusedFunctionObject, __signatures__),
+     READONLY,
+     0},
+    {0, 0, 0, 0, 0},
+};
+static PyMappingMethods __pyx_FusedFunction_mapping_methods = {
+    0,
+    (binaryfunc) __pyx_FusedFunction_getitem,
+    0,
+};
+static PyTypeObject __pyx_FusedFunctionType_type = {
     PyVarObject_HEAD_INIT(0, 0)
-    "cython_function_or_method",
-    sizeof(__pyx_CyFunctionObject),
+    "fused_cython_function",
+    sizeof(__pyx_FusedFunctionObject),
     0,
-    (destructor) __Pyx_CyFunction_dealloc,
+    (destructor) __pyx_FusedFunction_dealloc,
     0,
     0,
     0,
@@ -6165,36 +8846,32 @@ static PyTypeObject __pyx_CyFunctionType_type = {
 #else
     0,
 #endif
-    (reprfunc) __Pyx_CyFunction_repr,
     0,
     0,
     0,
+    &__pyx_FusedFunction_mapping_methods,
     0,
-    __Pyx_CyFunction_CallAsMethod,
+    (ternaryfunc) __pyx_FusedFunction_call,
     0,
     0,
     0,
     0,
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
     0,
-    (traverseproc) __Pyx_CyFunction_traverse,
-    (inquiry) __Pyx_CyFunction_clear,
+    (traverseproc) __pyx_FusedFunction_traverse,
+    (inquiry) __pyx_FusedFunction_clear,
     0,
-#if PY_VERSION_HEX < 0x030500A0
-    offsetof(__pyx_CyFunctionObject, func_weakreflist),
-#else
-    offsetof(PyCFunctionObject, m_weakreflist),
-#endif
     0,
     0,
-    __pyx_CyFunction_methods,
-    __pyx_CyFunction_members,
+    0,
+    0,
+    __pyx_FusedFunction_members,
     __pyx_CyFunction_getsets,
+    &__pyx_CyFunctionType_type,
     0,
+    __pyx_FusedFunction_descr_get,
     0,
-    __Pyx_CyFunction_descr_get,
     0,
-    offsetof(__pyx_CyFunctionObject, func_dict),
     0,
     0,
     0,
@@ -6211,40 +8888,55 @@ static PyTypeObject __pyx_CyFunctionType_type = {
     0,
 #endif
 };
-static int __pyx_CyFunction_init(void) {
-    __pyx_CyFunctionType = __Pyx_FetchCommonType(&__pyx_CyFunctionType_type);
-    if (__pyx_CyFunctionType == NULL) {
+static int __pyx_FusedFunction_init(void) {
+    __pyx_FusedFunctionType = __Pyx_FetchCommonType(&__pyx_FusedFunctionType_type);
+    if (__pyx_FusedFunctionType == NULL) {
         return -1;
     }
     return 0;
 }
-static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *func, size_t size, int pyobjects) {
-    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
-    m->defaults = PyObject_Malloc(size);
-    if (!m->defaults)
-        return PyErr_NoMemory();
-    memset(m->defaults, 0, size);
-    m->defaults_pyobjects = pyobjects;
-    return m->defaults;
-}
-static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *func, PyObject *tuple) {
-    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
-    m->defaults_tuple = tuple;
-    Py_INCREF(tuple);
-}
-static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *func, PyObject *dict) {
-    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
-    m->defaults_kwdict = dict;
-    Py_INCREF(dict);
-}
-static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *func, PyObject *dict) {
-    __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
-    m->func_annotations = dict;
-    Py_INCREF(dict);
+
+/* CalculateMetaclass */
+                static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases) {
+    Py_ssize_t i, nbases = PyTuple_GET_SIZE(bases);
+    for (i=0; i < nbases; i++) {
+        PyTypeObject *tmptype;
+        PyObject *tmp = PyTuple_GET_ITEM(bases, i);
+        tmptype = Py_TYPE(tmp);
+#if PY_MAJOR_VERSION < 3
+        if (tmptype == &PyClass_Type)
+            continue;
+#endif
+        if (!metaclass) {
+            metaclass = tmptype;
+            continue;
+        }
+        if (PyType_IsSubtype(metaclass, tmptype))
+            continue;
+        if (PyType_IsSubtype(tmptype, metaclass)) {
+            metaclass = tmptype;
+            continue;
+        }
+        PyErr_SetString(PyExc_TypeError,
+                        "metaclass conflict: "
+                        "the metaclass of a derived class "
+                        "must be a (non-strict) subclass "
+                        "of the metaclasses of all its bases");
+        return NULL;
+    }
+    if (!metaclass) {
+#if PY_MAJOR_VERSION < 3
+        metaclass = &PyClass_Type;
+#else
+        metaclass = &PyType_Type;
+#endif
+    }
+    Py_INCREF((PyObject*) metaclass);
+    return (PyObject*) metaclass;
 }
 
 /* Py3ClassCreate */
-              static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name,
+                static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name,
                                            PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc) {
     PyObject *ns;
     if (metaclass) {
@@ -6311,7 +9003,7 @@ static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObj
 }
 
 /* CodeObjectCache */
-              static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line) {
+                static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line) {
     int start = 0, mid = 0, end = count - 1;
     if (end >= 0 && code_line > entries[end].code_line) {
         return count;
@@ -6391,7 +9083,7 @@ static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object) {
 }
 
 /* AddTraceback */
-              #include "compile.h"
+                #include "compile.h"
 #include "frameobject.h"
 #include "traceback.h"
 static PyCodeObject* __Pyx_CreateCodeObjectForTraceback(
@@ -6464,37 +9156,15 @@ static void __Pyx_AddTraceback(const char *funcname, int c_line,
         0                    /*PyObject *locals*/
     );
     if (!py_frame) goto bad;
-    __Pyx_PyFrame_SetLineNumber(py_frame, py_line);
+    py_frame->f_lineno = py_line;
     PyTraceBack_Here(py_frame);
 bad:
     Py_XDECREF(py_code);
     Py_XDECREF(py_frame);
 }
 
-/* CIntFromPyVerify */
-              #define __PYX_VERIFY_RETURN_INT(target_type, func_type, func_value)\
-    __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 0)
-#define __PYX_VERIFY_RETURN_INT_EXC(target_type, func_type, func_value)\
-    __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 1)
-#define __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, exc)\
-    {\
-        func_type value = func_value;\
-        if (sizeof(target_type) < sizeof(func_type)) {\
-            if (unlikely(value != (func_type) (target_type) value)) {\
-                func_type zero = 0;\
-                if (exc && unlikely(value == (func_type)-1 && PyErr_Occurred()))\
-                    return (target_type) -1;\
-                if (is_unsigned && unlikely(value < zero))\
-                    goto raise_neg_overflow;\
-                else\
-                    goto raise_overflow;\
-            }\
-        }\
-        return (target_type) value;\
-    }
-
 /* CIntToPy */
-              static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
+                static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
     const long neg_one = (long) -1, const_zero = (long) 0;
     const int is_unsigned = neg_one > const_zero;
     if (is_unsigned) {
@@ -6502,18 +9172,14 @@ bad:
             return PyInt_FromLong((long) value);
         } else if (sizeof(long) <= sizeof(unsigned long)) {
             return PyLong_FromUnsignedLong((unsigned long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
             return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value);
-#endif
         }
     } else {
         if (sizeof(long) <= sizeof(long)) {
             return PyInt_FromLong((long) value);
-#ifdef HAVE_LONG_LONG
         } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
             return PyLong_FromLongLong((PY_LONG_LONG) value);
-#endif
         }
     }
     {
@@ -6524,8 +9190,30 @@ bad:
     }
 }
 
+/* CIntFromPyVerify */
+                #define __PYX_VERIFY_RETURN_INT(target_type, func_type, func_value)\
+    __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 0)
+#define __PYX_VERIFY_RETURN_INT_EXC(target_type, func_type, func_value)\
+    __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 1)
+#define __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, exc)\
+    {\
+        func_type value = func_value;\
+        if (sizeof(target_type) < sizeof(func_type)) {\
+            if (unlikely(value != (func_type) (target_type) value)) {\
+                func_type zero = 0;\
+                if (exc && unlikely(value == (func_type)-1 && PyErr_Occurred()))\
+                    return (target_type) -1;\
+                if (is_unsigned && unlikely(value < zero))\
+                    goto raise_neg_overflow;\
+                else\
+                    goto raise_overflow;\
+            }\
+        }\
+        return (target_type) value;\
+    }
+
 /* CIntFromPy */
-              static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
+                static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) {
     const int neg_one = (int) -1, const_zero = (int) 0;
     const int is_unsigned = neg_one > const_zero;
 #if PY_MAJOR_VERSION < 3
@@ -6592,10 +9280,8 @@ bad:
 #endif
             if (sizeof(int) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -6662,10 +9348,8 @@ bad:
 #endif
             if (sizeof(int) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(int, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -6714,7 +9398,7 @@ raise_neg_overflow:
 }
 
 /* CIntFromPy */
-              static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {
+                static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) {
     const long neg_one = (long) -1, const_zero = (long) 0;
     const int is_unsigned = neg_one > const_zero;
 #if PY_MAJOR_VERSION < 3
@@ -6781,10 +9465,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(long) <= sizeof(unsigned long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned long, PyLong_AsUnsignedLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x))
-#endif
             }
         } else {
 #if CYTHON_USE_PYLONG_INTERNALS
@@ -6851,10 +9533,8 @@ raise_neg_overflow:
 #endif
             if (sizeof(long) <= sizeof(long)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, long, PyLong_AsLong(x))
-#ifdef HAVE_LONG_LONG
             } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) {
                 __PYX_VERIFY_RETURN_INT_EXC(long, PY_LONG_LONG, PyLong_AsLongLong(x))
-#endif
             }
         }
         {
@@ -6903,7 +9583,7 @@ raise_neg_overflow:
 }
 
 /* SwapException */
-              #if CYTHON_FAST_THREAD_STATE
+                #if CYTHON_COMPILING_IN_CPYTHON
 static CYTHON_INLINE void __Pyx__ExceptionSwap(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) {
     PyObject *tmp_type, *tmp_value, *tmp_tb;
     tmp_type = tstate->exc_type;
@@ -6928,32 +9608,18 @@ static CYTHON_INLINE void __Pyx_ExceptionSwap(PyObject **type, PyObject **value,
 #endif
 
 /* PyObjectCallMethod1 */
-              static PyObject* __Pyx_PyObject_CallMethod1(PyObject* obj, PyObject* method_name, PyObject* arg) {
+                static PyObject* __Pyx_PyObject_CallMethod1(PyObject* obj, PyObject* method_name, PyObject* arg) {
     PyObject *method, *result = NULL;
     method = __Pyx_PyObject_GetAttrStr(obj, method_name);
-    if (unlikely(!method)) goto done;
-#if CYTHON_UNPACK_METHODS
+    if (unlikely(!method)) goto bad;
+#if CYTHON_COMPILING_IN_CPYTHON
     if (likely(PyMethod_Check(method))) {
         PyObject *self = PyMethod_GET_SELF(method);
         if (likely(self)) {
             PyObject *args;
             PyObject *function = PyMethod_GET_FUNCTION(method);
-            #if CYTHON_FAST_PYCALL
-            if (PyFunction_Check(function)) {
-                PyObject *args[2] = {self, arg};
-                result = __Pyx_PyFunction_FastCall(function, args, 2);
-                goto done;
-            }
-            #endif
-            #if CYTHON_FAST_PYCCALL
-            if (__Pyx_PyFastCFunction_Check(function)) {
-                PyObject *args[2] = {self, arg};
-                result = __Pyx_PyCFunction_FastCall(function, args, 2);
-                goto done;
-            }
-            #endif
             args = PyTuple_New(2);
-            if (unlikely(!args)) goto done;
+            if (unlikely(!args)) goto bad;
             Py_INCREF(self);
             PyTuple_SET_ITEM(args, 0, self);
             Py_INCREF(arg);
@@ -6968,13 +9634,13 @@ static CYTHON_INLINE void __Pyx_ExceptionSwap(PyObject **type, PyObject **value,
     }
 #endif
     result = __Pyx_PyObject_CallOneArg(method, arg);
-done:
+bad:
     Py_XDECREF(method);
     return result;
 }
 
 /* CoroutineBase */
-              #include <structmember.h>
+                #include <structmember.h>
 #include <frameobject.h>
 static PyObject *__Pyx_Coroutine_Send(PyObject *self, PyObject *value);
 static PyObject *__Pyx_Coroutine_Close(PyObject *self);
@@ -6995,38 +9661,41 @@ static int __Pyx_PyGen_FetchStopIterationValue(PyObject **pvalue) {
         return 0;
     }
     if (likely(et == PyExc_StopIteration)) {
-        if (!ev) {
-            Py_INCREF(Py_None);
-            value = Py_None;
-        }
 #if PY_VERSION_HEX >= 0x030300A0
-        else if (Py_TYPE(ev) == (PyTypeObject*)PyExc_StopIteration) {
+        if (ev && Py_TYPE(ev) == (PyTypeObject*)PyExc_StopIteration) {
             value = ((PyStopIterationObject *)ev)->value;
             Py_INCREF(value);
             Py_DECREF(ev);
+            Py_XDECREF(tb);
+            Py_DECREF(et);
+            *pvalue = value;
+            return 0;
         }
 #endif
-        else if (unlikely(PyTuple_Check(ev))) {
-            if (PyTuple_GET_SIZE(ev) >= 1) {
-#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
-                value = PyTuple_GET_ITEM(ev, 0);
-                Py_INCREF(value);
+        if (!ev || !PyObject_TypeCheck(ev, (PyTypeObject*)PyExc_StopIteration)) {
+            if (!ev) {
+                Py_INCREF(Py_None);
+                ev = Py_None;
+            } else if (PyTuple_Check(ev)) {
+                if (PyTuple_GET_SIZE(ev) >= 1) {
+                    PyObject *value;
+#if CYTHON_COMPILING_IN_CPYTHON
+                    value = PySequence_ITEM(ev, 0);
 #else
-                value = PySequence_ITEM(ev, 0);
+                    value = PyTuple_GET_ITEM(ev, 0);
+                    Py_INCREF(value);
 #endif
-            } else {
-                Py_INCREF(Py_None);
-                value = Py_None;
+                    Py_DECREF(ev);
+                    ev = value;
+                } else {
+                    Py_INCREF(Py_None);
+                    Py_DECREF(ev);
+                    ev = Py_None;
+                }
             }
-            Py_DECREF(ev);
-        }
-        else if (!PyObject_TypeCheck(ev, (PyTypeObject*)PyExc_StopIteration)) {
-            value = ev;
-        }
-        if (likely(value)) {
             Py_XDECREF(tb);
             Py_DECREF(et);
-            *pvalue = value;
+            *pvalue = ev;
             return 0;
         }
     } else if (!PyErr_GivenExceptionMatches(et, PyExc_StopIteration)) {
@@ -7103,7 +9772,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value) {
     }
     __Pyx_PyThreadState_assign
     if (value) {
-#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
+#if CYTHON_COMPILING_IN_PYPY
 #else
         if (self->exc_traceback) {
             PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback;
@@ -7124,7 +9793,7 @@ PyObject *__Pyx_Coroutine_SendEx(__pyx_CoroutineObject *self, PyObject *value) {
     if (retval) {
         __Pyx_ExceptionSwap(&self->exc_type, &self->exc_value,
                             &self->exc_traceback);
-#if CYTHON_COMPILING_IN_PYPY || CYTHON_COMPILING_IN_PYSTON
+#if CYTHON_COMPILING_IN_PYPY
 #else
         if (self->exc_traceback) {
             PyTracebackObject *tb = (PyTracebackObject *) self->exc_traceback;
@@ -7234,12 +9903,7 @@ static PyObject *__Pyx_Generator_Next(PyObject *self) {
     if (yf) {
         PyObject *ret;
         gen->is_running = 1;
-        #ifdef __Pyx_Generator_USED
-        if (__Pyx_Generator_CheckExact(yf)) {
-            ret = __Pyx_Generator_Next(yf);
-        } else
-        #endif
-            ret = Py_TYPE(yf)->tp_iternext(yf);
+        ret = Py_TYPE(yf)->tp_iternext(yf);
         gen->is_running = 0;
         if (likely(ret)) {
             return ret;
@@ -7428,10 +10092,8 @@ static void __Pyx_Coroutine_del(PyObject *self) {
 static PyObject *
 __Pyx_Coroutine_get_name(__pyx_CoroutineObject *self)
 {
-    PyObject *name = self->gi_name;
-    if (unlikely(!name)) name = Py_None;
-    Py_INCREF(name);
-    return name;
+    Py_INCREF(self->gi_name);
+    return self->gi_name;
 }
 static int
 __Pyx_Coroutine_set_name(__pyx_CoroutineObject *self, PyObject *value)
@@ -7455,10 +10117,8 @@ __Pyx_Coroutine_set_name(__pyx_CoroutineObject *self, PyObject *value)
 static PyObject *
 __Pyx_Coroutine_get_qualname(__pyx_CoroutineObject *self)
 {
-    PyObject *name = self->gi_qualname;
-    if (unlikely(!name)) name = Py_None;
-    Py_INCREF(name);
-    return name;
+    Py_INCREF(self->gi_qualname);
+    return self->gi_qualname;
 }
 static int
 __Pyx_Coroutine_set_qualname(__pyx_CoroutineObject *self, PyObject *value)
@@ -7479,9 +10139,8 @@ __Pyx_Coroutine_set_qualname(__pyx_CoroutineObject *self, PyObject *value)
     Py_XDECREF(tmp);
     return 0;
 }
-static __pyx_CoroutineObject *__Pyx__Coroutine_New(
-            PyTypeObject* type, __pyx_coroutine_body_t body, PyObject *closure,
-            PyObject *name, PyObject *qualname, PyObject *module_name) {
+static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject* type, __pyx_coroutine_body_t body,
+                                                   PyObject *closure, PyObject *name, PyObject *qualname) {
     __pyx_CoroutineObject *gen = PyObject_GC_New(__pyx_CoroutineObject, type);
     if (gen == NULL)
         return NULL;
@@ -7500,14 +10159,12 @@ static __pyx_CoroutineObject *__Pyx__Coroutine_New(
     gen->gi_qualname = qualname;
     Py_XINCREF(name);
     gen->gi_name = name;
-    Py_XINCREF(module_name);
-    gen->gi_modulename = module_name;
     PyObject_GC_Track(gen);
     return gen;
 }
 
 /* PatchModuleWithCoroutine */
-                  static PyObject* __Pyx_Coroutine_patch_module(PyObject* module, const char* py_code) {
+                    static PyObject* __Pyx_Coroutine_patch_module(PyObject* module, const char* py_code) {
 #if defined(__Pyx_Generator_USED) || defined(__Pyx_Coroutine_USED)
     int result;
     PyObject *globals, *result_obj;
@@ -7547,7 +10204,7 @@ ignore:
 }
 
 /* PatchGeneratorABC */
-                  #if defined(__Pyx_Generator_USED) || defined(__Pyx_Coroutine_USED)
+                    #if defined(__Pyx_Generator_USED) || defined(__Pyx_Coroutine_USED)
 static PyObject* __Pyx_patch_abc_module(PyObject *module);
 static PyObject* __Pyx_patch_abc_module(PyObject *module) {
     module = __Pyx_Coroutine_patch_module(
@@ -7601,7 +10258,7 @@ static int __Pyx_patch_abc(void) {
 }
 
 /* Generator */
-                  static PyMethodDef __pyx_Generator_methods[] = {
+                    static PyMethodDef __pyx_Generator_methods[] = {
     {"send", (PyCFunction) __Pyx_Coroutine_Send, METH_O,
      (char*) PyDoc_STR("send(arg) -> send 'arg' into generator,\nreturn next yielded value or raise StopIteration.")},
     {"throw", (PyCFunction) __Pyx_Coroutine_Throw, METH_VARARGS,
@@ -7690,7 +10347,7 @@ static int __pyx_Generator_init(void) {
 }
 
 /* CheckBinaryVersion */
-                  static int __Pyx_check_binary_version(void) {
+                    static int __Pyx_check_binary_version(void) {
     char ctversion[4], rtversion[4];
     PyOS_snprintf(ctversion, 4, "%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
     PyOS_snprintf(rtversion, 4, "%s", Py_GetVersion());
@@ -7706,7 +10363,7 @@ static int __pyx_Generator_init(void) {
 }
 
 /* InitStrings */
-                  static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
+                    static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
     while (t->p) {
         #if PY_MAJOR_VERSION < 3
         if (t->is_unicode) {
@@ -7807,9 +10464,7 @@ static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject* x) {
    else return PyObject_IsTrue(x);
 }
 static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
-#if CYTHON_USE_TYPE_SLOTS
   PyNumberMethods *m;
-#endif
   const char *name = NULL;
   PyObject *res = NULL;
 #if PY_MAJOR_VERSION < 3
@@ -7818,9 +10473,8 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
   if (PyLong_Check(x))
 #endif
     return __Pyx_NewRef(x);
-#if CYTHON_USE_TYPE_SLOTS
   m = Py_TYPE(x)->tp_as_number;
-  #if PY_MAJOR_VERSION < 3
+#if PY_MAJOR_VERSION < 3
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Int(x);
@@ -7829,14 +10483,11 @@ static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) {
     name = "long";
     res = PyNumber_Long(x);
   }
-  #else
+#else
   if (m && m->nb_int) {
     name = "int";
     res = PyNumber_Long(x);
   }
-  #endif
-#else
-  res = PyNumber_Int(x);
 #endif
   if (res) {
 #if PY_MAJOR_VERSION < 3
diff --git a/cutadapt/_seqio.pyx b/src/cutadapt/_seqio.pyx
similarity index 64%
rename from cutadapt/_seqio.pyx
rename to src/cutadapt/_seqio.pyx
index f27a359..a76a3b5 100644
--- a/cutadapt/_seqio.pyx
+++ b/src/cutadapt/_seqio.pyx
@@ -4,6 +4,104 @@ from __future__ import print_function, division, absolute_import
 from xopen import xopen
 from .seqio import _shorten, FormatError, SequenceReader
 
+cimport cython
+
+# It would be nice to be able to have the first parameter be a
+# unsigned char[:] (memory view), but this fails with a BufferError
+# when a bytes object is passed in.
+# See <https://stackoverflow.com/questions/28203670/>
+
+ctypedef fused bytes_or_bytearray:
+	bytes
+	bytearray
+
+
+#@cython.boundscheck(False)
+def head(bytes_or_bytearray buf, Py_ssize_t lines):
+	"""
+	Skip forward by a number of lines in the given buffer and return
+	how many bytes this corresponds to.
+	"""
+	cdef:
+		Py_ssize_t pos = 0
+		Py_ssize_t linebreaks_seen = 0
+		Py_ssize_t length = len(buf)
+		unsigned char* data = buf
+
+	while linebreaks_seen < lines and pos < length:
+		if data[pos] == '\n':
+			linebreaks_seen += 1
+		pos += 1
+	return pos
+
+
+def fastq_head(bytes_or_bytearray buf, Py_ssize_t end=-1):
+	"""
+	Return an integer length such that buf[:length] contains the highest
+	possible number of complete four-line records.
+
+	If end is -1, the full buffer is searched. Otherwise only buf[:end].
+	"""
+	cdef:
+		Py_ssize_t pos = 0
+		Py_ssize_t linebreaks = 0
+		Py_ssize_t length = len(buf)
+		unsigned char* data = buf
+		Py_ssize_t record_start = 0
+
+	if end != -1:
+		length = min(length, end)
+	while True:
+		while pos < length and data[pos] != '\n':
+			pos += 1
+		if pos == length:
+			break
+		pos += 1
+		linebreaks += 1
+		if linebreaks == 4:
+			linebreaks = 0
+			record_start = pos
+
+	# Reached the end of the data block
+	return record_start
+
+
+def two_fastq_heads(bytes_or_bytearray buf1, bytes_or_bytearray buf2, Py_ssize_t end1, Py_ssize_t end2):
+	"""
+	Skip forward in the two buffers by multiples of four lines.
+
+	Return a tuple (length1, length2) such that buf1[:length1] and
+	buf2[:length2] contain the same number of lines (where the
+	line number is divisible by four).
+	"""
+	cdef:
+		Py_ssize_t pos1 = 0, pos2 = 0
+		Py_ssize_t linebreaks = 0
+		unsigned char* data1 = buf1
+		unsigned char* data2 = buf2
+		Py_ssize_t record_start1 = 0
+		Py_ssize_t record_start2 = 0
+
+	while True:
+		while pos1 < end1 and data1[pos1] != '\n':
+			pos1 += 1
+		if pos1 == end1:
+			break
+		pos1 += 1
+		while pos2 < end2 and data2[pos2] != '\n':
+			pos2 += 1
+		if pos2 == end2:
+			break
+		pos2 += 1
+		linebreaks += 1
+		if linebreaks == 4:
+			linebreaks = 0
+			record_start1 = pos1
+			record_start2 = pos2
+
+	# Hit the end of the data block
+	return record_start1, record_start2
+
 
 cdef class Sequence(object):
 	"""
diff --git a/cutadapt/_version.py b/src/cutadapt/_version.py
similarity index 82%
rename from cutadapt/_version.py
rename to src/cutadapt/_version.py
index 22c1dd2..a83aa8a 100644
--- a/cutadapt/_version.py
+++ b/src/cutadapt/_version.py
@@ -11,8 +11,8 @@ version_json = '''
 {
  "dirty": false,
  "error": null,
- "full-revisionid": "8fcb956f115c6c1f404cd2f2da64bcb443ec0c3e",
- "version": "1.13"
+ "full-revisionid": "8d0b61222e77973592bd2fb9e2ce57445fccf8dd",
+ "version": "1.15"
 }
 '''  # END VERSION_JSON
 
diff --git a/cutadapt/adapters.py b/src/cutadapt/adapters.py
similarity index 83%
rename from cutadapt/adapters.py
rename to src/cutadapt/adapters.py
index 47217ff..17735d7 100644
--- a/cutadapt/adapters.py
+++ b/src/cutadapt/adapters.py
@@ -6,11 +6,11 @@ The ...Adapter classes are responsible for finding adapters.
 The ...Match classes trim the reads.
 """
 from __future__ import print_function, division, absolute_import
-import sys
 import re
 from collections import defaultdict
 from cutadapt import align, colorspace
-from cutadapt.seqio import ColorspaceSequence, FastaReader
+from cutadapt.seqio import FastaReader
+
 
 # Constants for the find_best_alignment function.
 # The function is called with SEQ1 as the adapter, SEQ2 as the read.
@@ -34,7 +34,7 @@ def parse_braces(sequence):
 	# Simple DFA with four states, encoded in prev
 	result = ''
 	prev = None
-	for s in re.split('(\{|\})', sequence):
+	for s in re.split('([{}])', sequence):
 		if s == '':
 			continue
 		if prev is None:
@@ -86,6 +86,7 @@ class AdapterParser(object):
 		"""
 		if name is None:
 			name, spec = self._extract_name(spec)
+		orig_spec = spec
 		types = dict(back=BACK, front=FRONT, anywhere=ANYWHERE)
 		if cmdline_type not in types:
 			raise ValueError('cmdline_type cannot be {0!r}'.format(cmdline_type))
@@ -130,14 +131,16 @@ class AdapterParser(object):
 				if where == BACK:
 					front_anchored = True
 
+				require_both = True if not front_anchored and not back_anchored else None
 				return LinkedAdapter(sequence1, sequence2, name=name,
 					front_anchored=front_anchored, back_anchored=back_anchored,
+					require_both=require_both,
 					**self.constructor_args)
 		if front_anchored and back_anchored:
-			raise ValueError('Cannot anchor both ends at the same time')
+			raise ValueError('Trying to use both "^" and "$" in adapter specification {!r}'.format(orig_spec))
 		if front_anchored:
 			if where == BACK:
-				raise ValueError("Cannot anchor 3' adapter at 5' end")
+				raise ValueError("Cannot anchor the 3' adapter at its 5' end")
 			where = PREFIX
 		elif back_anchored:
 			if where == FRONT:
@@ -192,6 +195,93 @@ class AdapterParser(object):
 		return adapters
 
 
+def returns_defaultdict_int():
+	# We need this function to make EndStatistics picklable.
+	# Even a @staticmethod of EndStatistics is not sufficient
+	# as that is not picklable before Python 3.5.
+	return defaultdict(int)
+
+
+class EndStatistics(object):
+	"""Statistics about the 5' or 3' end"""
+
+	def __init__(self, adapter):
+		self.where = adapter.where
+		self.max_error_rate = adapter.max_error_rate
+		self.sequence = adapter.sequence
+		self.has_wildcards = adapter.adapter_wildcards
+		# self.errors[l][e] == n iff n times a sequence of length l matching at e errors was removed
+		self.errors = defaultdict(returns_defaultdict_int)
+		self._remove_before = adapter.remove_before
+		self.adjacent_bases = {'A': 0, 'C': 0, 'G': 0, 'T': 0, '': 0}
+
+	def __iadd__(self, other):
+		if self.where != other.where or self.max_error_rate != other.max_error_rate or self.sequence != other.sequence:
+			raise RuntimeError('Incompatible EndStatistics, cannot be added')
+		for base in ('A', 'C', 'G', 'T', ''):
+			self.adjacent_bases[base] += other.adjacent_bases[base]
+		for length, error_dict in other.errors.items():
+			for errors in error_dict:
+				self.errors[length][errors] += other.errors[length][errors]
+		return self
+
+	@property
+	def lengths(self):
+		# Python 2.6 has no dict comprehension
+		d = dict((length, sum(errors.values())) for length, errors in self.errors.items())
+		return d
+
+	def random_match_probabilities(self, gc_content):
+		"""
+		Estimate probabilities that this adapter end matches a
+		random sequence. Indels are not taken into account.
+
+		Returns a list p, where p[i] is the probability that
+		i bases of this adapter match a random sequence with
+		GC content gc_content.
+
+		The where parameter is necessary for linked adapters to
+		specify which (front or back) of the two adapters is meant.
+		"""
+		seq = self.sequence
+		if self._remove_before:
+			seq = seq[::-1]
+		allowed_bases = 'CGRYSKMBDHVN' if self.has_wildcards else 'GC'
+		p = 1
+		probabilities = [p]
+		for i, c in enumerate(seq):
+			if c in allowed_bases:
+				p *= gc_content / 2.
+			else:
+				p *= (1 - gc_content) / 2
+			probabilities.append(p)
+		return probabilities
+
+
+class AdapterStatistics(object):
+	"""
+	Statistics about an adapter. An adapter can work on the 5' end (front)
+	or 3' end (back) of a read, and statistics for that are captured
+	separately.
+	"""
+
+	def __init__(self, adapter, adapter2=None, where=None):
+		self.name = adapter.name
+		self.where = where if where is not None else adapter.where
+		self.front = EndStatistics(adapter)
+		if adapter2 is None:
+			self.back = EndStatistics(adapter)
+		else:
+			self.back = EndStatistics(adapter2)
+
+	def __iadd__(self, other):
+		if self.where != other.where:  # TODO self.name != other.name or
+			raise ValueError('incompatible objects')
+		self.front += other.front
+		self.back += other.back
+		return self
+
+
 class Match(object):
 	"""
 	Representation of a single adapter matched to a single read.
@@ -299,10 +389,10 @@ class Match(object):
 	def update_statistics(self, statistics):
 		"""Update AdapterStatistics in place"""
 		if self.remove_before:
-			statistics.errors_front[self.rstop][self.errors] += 1
+			statistics.front.errors[self.rstop][self.errors] += 1
 		else:
-			statistics.errors_back[len(self.read) - len(self._trimmed_read)][self.errors] += 1
-			statistics.adjacent_bases[self.adjacent_base] += 1
+			statistics.back.errors[len(self.read) - len(self._trimmed_read)][self.errors] += 1
+			statistics.back.adjacent_bases[self.adjacent_base] += 1
 
 
 class ColorspaceMatch(Match):
@@ -333,9 +423,9 @@ class ColorspaceMatch(Match):
 	def update_statistics(self, statistics):
 		"""Update AdapterStatistics in place"""
 		if self.remove_before:
-			statistics.errors_front[self.rstop][self.errors] += 1
+			statistics.front.errors[self.rstop][self.errors] += 1
 		else:
-			statistics.errors_back[len(self.read) - len(self._trimmed_read)][self.errors] += 1
+			statistics.back.errors[len(self.read) - len(self._trimmed_read)][self.errors] += 1
 
 
 def _generate_adapter_name(_start=[1]):
@@ -481,29 +571,8 @@ class Adapter(object):
 	def __len__(self):
 		return len(self.sequence)
 
-	def random_match_probabilities(self, gc_content):
-		"""
-		Estimate probabilities that this adapter matches a
-		random sequence. Indels are not taken into account.
-
-		Returns a list p, where p[i] is the probability that
-		i bases of this adapter match a random sequence with
-		GC content gc_content.
-		"""
-		if self.remove_before:
-			seq = self.sequence[::-1]
-		else:
-			seq = self.sequence
-		allowed_bases = 'CGRYSKMBDHVN' if self.adapter_wildcards else 'GC'
-		p = 1
-		probabilities = [p]
-		for i, c in enumerate(seq):
-			if c in allowed_bases:
-				p *= gc_content / 2.
-			else:
-				p *= (1 - gc_content) / 2
-			probabilities.append(p)
-		return probabilities
+	def create_statistics(self):
+		return AdapterStatistics(self)
 
 
 class ColorspaceAdapter(Adapter):
@@ -580,8 +649,6 @@ class LinkedMatch(object):
 		self.front_match = front_match
 		self.back_match = back_match
 		self.adapter = adapter
-		assert not adapter.front_anchored or front_match is not None
-		assert not adapter.back_anchored or back_match is not None
 
 	def __repr__(self):
 		return '<LinkedMatch(front_match={0!r}, back_match={1}, adapter={2})>'.format(
@@ -590,7 +657,7 @@ class LinkedMatch(object):
 	@property
 	def matches(self):
 		"""Number of matching bases"""
-		m = getattr(self.front_match, 'matches', [])
+		m = getattr(self.front_match, 'matches', 0)
 		if self.back_match is not None:
 			m += self.back_match.matches
 		return m
@@ -611,23 +678,29 @@ class LinkedMatch(object):
 	def update_statistics(self, statistics):
 		"""Update AdapterStatistics in place"""
 		if self.front_match:
-			statistics.errors_front[self.front_match.rstop][self.front_match.errors] += 1
+			statistics.front.errors[self.front_match.rstop][self.front_match.errors] += 1
 		if self.back_match:
-			statistics.errors_back[len(self.back_match.read) - self.back_match.rstop][self.back_match.errors] += 1
+			statistics.back.errors[len(self.back_match.read) - self.back_match.rstart][self.back_match.errors] += 1
 
 
 class LinkedAdapter(object):
 	"""
 	"""
 	def __init__(self, front_sequence, back_sequence,
-			front_anchored=True, back_anchored=False, name=None, **kwargs):
+			front_anchored=True, back_anchored=False, require_both=None, name=None, **kwargs):
 		"""
+		require_both -- require both adapters to match. If not specified, the default is to
+			require only anchored adapters to match.
 		kwargs are passed on to individual Adapter constructors
 		"""
 		where1 = PREFIX if front_anchored else FRONT
 		where2 = SUFFIX if back_anchored else BACK
-		self.front_anchored = front_anchored
-		self.back_anchored = back_anchored
+		if require_both:
+			self._require_back_match = True
+			self._require_front_match = True
+		else:
+			self._require_front_match = front_anchored
+			self._require_back_match = back_anchored
 
 		# The following attributes are needed for the report
 		self.where = LINKED
@@ -642,16 +715,20 @@ class LinkedAdapter(object):
 	def match_to(self, read):
 		"""
 		Match the linked adapters against the given read. Any anchored adapters are
-		required to exist for a successful match.
+		required to exist for a successful match. If both adapters are unanchored,
+		both need to match.
 		"""
 		front_match = self.front_adapter.match_to(read)
-		if self.front_anchored and front_match is None:
+		if self._require_front_match and front_match is None:
 			return None
 
 		if front_match is not None:
 			# TODO statistics
 			read = front_match.trimmed()
 		back_match = self.back_adapter.match_to(read)
-		if back_match is None and (self.back_anchored or front_match is None):
+		if back_match is None and (self._require_back_match or front_match is None):
 			return None
 		return LinkedMatch(front_match, back_match, self)
+
+	def create_statistics(self):
+		return AdapterStatistics(self.front_adapter, self.back_adapter, where=LINKED)
diff --git a/cutadapt/align.py b/src/cutadapt/align.py
similarity index 100%
rename from cutadapt/align.py
rename to src/cutadapt/align.py
diff --git a/cutadapt/colorspace.py b/src/cutadapt/colorspace.py
similarity index 100%
rename from cutadapt/colorspace.py
rename to src/cutadapt/colorspace.py
diff --git a/cutadapt/compat.py b/src/cutadapt/compat.py
similarity index 100%
rename from cutadapt/compat.py
rename to src/cutadapt/compat.py
diff --git a/cutadapt/filters.py b/src/cutadapt/filters.py
similarity index 57%
rename from cutadapt/filters.py
rename to src/cutadapt/filters.py
index 1fd0428..ddea187 100644
--- a/cutadapt/filters.py
+++ b/src/cutadapt/filters.py
@@ -82,55 +82,36 @@ class Redirector(object):
 
 class PairedRedirector(object):
 	"""
-	Redirect discarded reads to the given writer. This is for paired-end reads,
-	using the 'new-style' filtering where both reads are inspected. That is,
-	the entire pair is discarded if at least 1 or 2 of the reads match the
-	filtering criteria.
+	Redirect paired-end reads matching a filtering criterion to a writer.
+	Different filtering styles are supported, differing by which of the
+	two reads in a pair have to fulfill the filtering criterion.
 	"""
-	def __init__(self, writer, filter, min_affected=1):
+	def __init__(self, writer, filter, pair_filter_mode='any'):
 		"""
-		min_affected -- values 1 and 2 are allowed.
-			1 means: the pair is discarded if any read matches
-			2 means: the pair is discarded if both reads match
+		pair_filter_mode -- these values are allowed:
+			'any': The pair is discarded if any read matches.
+			'both': The pair is discarded if both reads match.
+			'first': The pair is discarded if the first read matches
+				('legacy' mode, backwards compatibility). With 'first', the
+				second read is not inspected.
 		"""
-		if min_affected not in (1, 2):
-			raise ValueError("min_affected must be 1 or 2")
-		self.filtered = 0
-		self.writer = writer
-		self.filter = filter
-		self._min_affected = min_affected
-		self.written = 0  # no of written reads or read pairs  TODO move to writer
-		self.written_bp = [0, 0]
-
-	def __call__(self, read1, read2):
-		if self.filter(read1) + self.filter(read2) >= self._min_affected:
-			self.filtered += 1
-			# discard read
-			if self.writer is not None:
-				self.writer.write(read1, read2)
-				self.written += 1
-				self.written_bp[0] += len(read1)
-				self.written_bp[1] += len(read2)
-			return DISCARD
-		return KEEP
-
-
-class LegacyPairedRedirector(object):
-	"""
-	Redirect discarded reads to the given writer. This is for paired-end reads,
-	using the 'legacy' filtering mode (backwards compatibility). That is, if
-	the first read matches the filtering criteria, the pair is discarded. The
-	second read is not inspected.
-	"""
-	def __init__(self, writer, filter):
+		if pair_filter_mode not in ('any', 'both', 'first'):
+			raise ValueError("pair_filter_mode must be 'any', 'both' or 'first'")
 		self.filtered = 0
 		self.writer = writer
 		self.filter = filter
 		self.written = 0  # no of written reads or read pairs  TODO move to writer
 		self.written_bp = [0, 0]
+		if pair_filter_mode == 'any':
+			self._is_filtered = lambda r1, r2: self.filter(r1) or self.filter(r2)
+		elif pair_filter_mode == 'both':
+			self._is_filtered = lambda r1, r2: self.filter(r1) and self.filter(r2)
+		else:
+			assert pair_filter_mode == 'first'
+			self._is_filtered = lambda r1, r2: self.filter(r1)
 
 	def __call__(self, read1, read2):
-		if self.filter(read1):
+		if self._is_filtered(read1, read2):
 			self.filtered += 1
 			# discard read
 			if self.writer is not None:
@@ -143,7 +124,6 @@ class LegacyPairedRedirector(object):
 
 
 class TooShortReadFilter(object):
-	# TODO paired_outfile is left at its default value None (read2 is silently discarded)
 	def __init__(self, minimum_length):
 		self.minimum_length = minimum_length
 
@@ -224,31 +204,114 @@ class Demultiplexer(object):
 		self.colorspace = colorspace
 		self.qualities = qualities
 
-	def __call__(self, read1, read2=None):
-		if read2 is None:
-			# single-end read
-			if read1.match is None:
-				if self.untrimmed_writer is None and self.untrimmed_path is not None:
-					self.untrimmed_writer = seqio.open(self.untrimmed_path,
-						mode='w', colorspace=self.colorspace, qualities=self.qualities)
-				if self.untrimmed_writer is not None:
-					self.written += 1
-					self.written_bp[0] += len(read1)
-					self.untrimmed_writer.write(read1)
-			else:
-				name = read1.match.adapter.name
-				if name not in self.writers:
-					self.writers[name] = seqio.open(self.template.replace('{name}', name),
-						mode='w', colorspace=self.colorspace, qualities=self.qualities)
+	def write(self, read, match):
+		"""
+		Write the read to the proper output file according to the match
+		"""
+		if match is None:
+			if self.untrimmed_writer is None and self.untrimmed_path is not None:
+				self.untrimmed_writer = seqio.open(self.untrimmed_path,
+					mode='w', colorspace=self.colorspace, qualities=self.qualities)
+			if self.untrimmed_writer is not None:
 				self.written += 1
-				self.written_bp[0] += len(read1)
-				self.writers[name].write(read1)
-			return DISCARD
+				self.written_bp[0] += len(read)
+				self.untrimmed_writer.write(read)
 		else:
-			assert False, "Not supported"  # pragma: no cover
+			name = match.adapter.name
+			if name not in self.writers:
+				self.writers[name] = seqio.open(self.template.replace('{name}', name),
+					mode='w', colorspace=self.colorspace, qualities=self.qualities)
+			self.written += 1
+			self.written_bp[0] += len(read)
+			self.writers[name].write(read)
+
+	def __call__(self, read1, read2=None):
+		assert read2 is None
+		self.write(read1, read1.match)
+		return DISCARD
 
 	def close(self):
 		for w in self.writers.values():
 			w.close()
 		if self.untrimmed_writer is not None:
 			self.untrimmed_writer.close()
+
+
+class PairedEndDemultiplexer(object):
+	"""
+	Demultiplex trimmed paired-end reads. Reads are written to different output files
+	depending on which adapter (in read 1) matches.
+	"""
+	def __init__(self, path_template, path_paired_template, untrimmed_path, untrimmed_paired_path,
+			colorspace, qualities):
+		"""
+		The path templates must contain the string '{name}', which will be replaced
+		with the name of the adapter to form the final output path.
+		Read pairs without an adapter match are written to the files named by
+		untrimmed_path.
+		"""
+		self._demultiplexer1 = Demultiplexer(path_template, untrimmed_path, colorspace, qualities)
+		self._demultiplexer2 = Demultiplexer(path_paired_template, untrimmed_paired_path,
+			colorspace, qualities)
+
+	@property
+	def written(self):
+		return self._demultiplexer1.written + self._demultiplexer2.written
+
+	@property
+	def written_bp(self):
+		return [self._demultiplexer1.written_bp[0], self._demultiplexer2.written_bp[0]]
+
+	def __call__(self, read1, read2):
+		assert read2 is not None
+		self._demultiplexer1.write(read1, read1.match)
+		self._demultiplexer2.write(read2, read1.match)
+
+	def close(self):
+		self._demultiplexer1.close()
+		self._demultiplexer1.close()
+
+
+class RestFileWriter(object):
+	def __init__(self, file):
+		self.file = file
+
+	def __call__(self, read, read2=None):
+		if read.match:
+			rest = read.match.rest()
+			if len(rest) > 0:
+				print(rest, read.name, file=self.file)
+		return KEEP
+
+
+class WildcardFileWriter(object):
+	def __init__(self, file):
+		self.file = file
+
+	def __call__(self, read, read2=None):
+		if read.match:
+			print(read.match.wildcards(), read.name, file=self.file)
+		return KEEP
+
+
+class InfoFileWriter(object):
+	def __init__(self, file):
+		self.file = file
+
+	def __call__(self, read, read2=None):
+		matches = []
+		r = read
+		while r.match is not None:
+			matches.append(r.match)
+			r = r.match.read
+		matches = matches[::-1]
+		if matches:
+			for match in matches:
+				info_record = match.get_info_record()
+				print(*info_record, sep='\t', file=self.file)
+		else:
+			seq = read.sequence
+			qualities = read.qualities if read.qualities is not None else ''
+			print(read.name, -1, seq, qualities, sep='\t', file=self.file)
+
+		return KEEP
diff --git a/cutadapt/modifiers.py b/src/cutadapt/modifiers.py
similarity index 78%
rename from cutadapt/modifiers.py
rename to src/cutadapt/modifiers.py
index 3c5c64f..bd7a8cf 100644
--- a/cutadapt/modifiers.py
+++ b/src/cutadapt/modifiers.py
@@ -7,31 +7,11 @@ need to be stored, and as a class with a __call__ method if there are parameters
 """
 from __future__ import print_function, division, absolute_import
 import re
-from collections import defaultdict
+from collections import OrderedDict
 from cutadapt.qualtrim import quality_trim_index, nextseq_trim_index
 from cutadapt.compat import maketrans
 
 
-class AdapterStatistics(object):
-	def __init__(self, adapter):
-		self.adapter = adapter
-		self.errors_front = defaultdict(lambda: defaultdict(int))
-		self.errors_back = defaultdict(lambda: defaultdict(int))
-		self.adjacent_bases = {'A': 0, 'C': 0, 'G': 0, 'T': 0, '': 0}
-
-	@property
-	def lengths_front(self):
-		# Python 2.6 has no dict comprehension
-		d = dict((length, sum(errors.values())) for length, errors in self.errors_front.items())
-		return d
-
-	@property
-	def lengths_back(self):
-		# Python 2.6 has no dict comprehension
-		d = dict((length, sum(errors.values())) for length, errors in self.errors_back.items())
-		return d
-
-
 class AdapterCutter(object):
 	"""
 	Repeatedly find one of multiple adapters in reads.
@@ -39,8 +19,7 @@ class AdapterCutter(object):
 	times parameter.
 	"""
 
-	def __init__(self, adapters, times=1, wildcard_file=None, info_file=None,
-			rest_writer=None, action='trim'):
+	def __init__(self, adapters, times=1, action='trim'):
 		"""
 		adapters -- list of Adapter objects
 
@@ -48,12 +27,9 @@ class AdapterCutter(object):
 		"""
 		self.adapters = adapters
 		self.times = times
-		self.wildcard_file = wildcard_file
-		self.info_file = info_file
-		self.rest_writer = rest_writer
 		self.action = action
 		self.with_adapters = 0
-		self.adapter_statistics = dict((a, AdapterStatistics(a)) for a in adapters)  # Python 2.6
+		self.adapter_statistics = OrderedDict((a, a.create_statistics()) for a in adapters)
 
 	def _best_match(self, read):
 		"""
@@ -75,30 +51,6 @@ class AdapterCutter(object):
 				best = match
 		return best
 
-	def _write_info(self, read, matches):
-		"""
-		Write to the info, wildcard and rest files.
-		"""
-		# TODO
-		# This design with a read having a .match attribute and
-		# a match having a .read attribute is really confusing.
-		match = read.match
-		if self.rest_writer and match:
-			self.rest_writer.write(match)
-
-		if self.wildcard_file and match:
-			print(match.wildcards(), read.name, file=self.wildcard_file)
-
-		if self.info_file:
-			if matches:
-				for match in matches:
-					info_record = match.get_info_record()
-					print(*info_record, sep='\t', file=self.info_file)
-			else:
-				seq = read.sequence
-				qualities = read.qualities if read.qualities is not None else ''
-				print(read.name, -1, seq, qualities, sep='\t', file=self.info_file)
-
 	def __call__(self, read):
 		"""
 		Cut found adapters from a single read. Return modified read.
@@ -123,11 +75,11 @@ class AdapterCutter(object):
 				break
 			matches.append(match)
 			trimmed_read = match.trimmed()
+			trimmed_read.match = match
 			match.update_statistics(self.adapter_statistics[match.adapter])
 
 		if not matches:
 			trimmed_read.match = None
-			self._write_info(trimmed_read, [])
 			return trimmed_read
 
 		if __debug__:
@@ -141,7 +93,7 @@ class AdapterCutter(object):
 			masked_sequence = trimmed_read.sequence
 			for match in sorted(matches, reverse=True, key=lambda m: m.astart):
 				ns = 'N' * (len(match.read.sequence) -
-							len(match.trimmed().sequence))  # TODO is this correct? -> stats?
+							len(match.trimmed().sequence))
 				# add N depending on match position
 				if match.remove_before:
 					masked_sequence = ns + masked_sequence
@@ -153,9 +105,8 @@ class AdapterCutter(object):
 			assert len(trimmed_read.sequence) == len(read)
 		elif self.action is None:
 			trimmed_read = read
+			trimmed_read.match = matches[-1]
 
-		trimmed_read.match = matches[-1]
-		self._write_info(trimmed_read, matches)
 		self.with_adapters += 1
 		return trimmed_read
 
diff --git a/src/cutadapt/pipeline.py b/src/cutadapt/pipeline.py
new file mode 100644
index 0000000..405ed3a
--- /dev/null
+++ b/src/cutadapt/pipeline.py
@@ -0,0 +1,651 @@
+from __future__ import print_function, division, absolute_import
+
+import io
+import os
+import re
+import sys
+import logging
+import functools
+from multiprocessing import Process, Pipe, Queue
+import multiprocessing.connection
+import traceback
+
+from xopen import xopen
+
+from . import seqio
+from .modifiers import ZeroCapper
+from .report import Statistics
+from .filters import (Redirector, PairedRedirector, NoFilter, PairedNoFilter, InfoFileWriter,
+	RestFileWriter, WildcardFileWriter, TooShortReadFilter, TooLongReadFilter, NContentFilter,
+	DiscardTrimmedFilter, DiscardUntrimmedFilter, Demultiplexer, PairedEndDemultiplexer)
+from .seqio import read_chunks_from_file, read_paired_chunks
+
+logger = logging.getLogger()
+
+
+class OutputFiles(object):
+	"""
+	The attributes are open file-like objects except when demultiplex is True. In that case,
+	untrimmed, untrimmed2 are file names, and out and out2 are file name templates
+	containing '{name}'.
+	If interleaved is True, then out is written interleaved.
+	Files may also be None.
+	"""
+	# TODO interleaving for the other file pairs (too_short, too_long, untrimmed)?
+	def __init__(self,
+			out=None,
+			out2=None,
+			untrimmed=None,
+			untrimmed2=None,
+			too_short=None,
+			too_short2=None,
+			too_long=None,
+			too_long2=None,
+			info=None,
+			rest=None,
+			wildcard=None,
+			demultiplex=False,
+			interleaved=False,
+	):
+		self.out = out
+		self.out2 = out2
+		self.untrimmed = untrimmed
+		self.untrimmed2 = untrimmed2
+		self.too_short = too_short
+		self.too_short2 = too_short2
+		self.too_long = too_long
+		self.too_long2 = too_long2
+		self.info = info
+		self.rest = rest
+		self.wildcard = wildcard
+		self.demultiplex = demultiplex
+		self.interleaved = interleaved
+
+	def __iter__(self):
+		yield self.out
+		yield self.out2
+		yield self.untrimmed
+		yield self.untrimmed2
+		yield self.too_short
+		yield self.too_short2
+		yield self.too_long
+		yield self.too_long2
+		yield self.info
+		yield self.rest
+		yield self.wildcard
+
+
+class Pipeline(object):
+	"""
+	Processing pipeline that loops over reads and applies modifiers and filters
+	"""
+	should_warn_legacy = False
+	n_adapters = 0
+
+	def __init__(self, ):
+		self._close_files = []
+		self._reader = None
+		self._filters = []
+		self._modifiers = []
+		self._colorspace = None
+		self._outfiles = None
+		self._demultiplexer = None
+
+	def set_input(self, file1, file2=None, qualfile=None, colorspace=False, fileformat=None,
+			interleaved=False):
+		self._reader = seqio.open(file1, file2, qualfile, colorspace, fileformat,
+			interleaved, mode='r')
+		self._colorspace = colorspace
+		# Special treatment: Disable zero-capping if no qualities are available
+		if not self._reader.delivers_qualities:
+			self._modifiers = [m for m in self._modifiers if not isinstance(m, ZeroCapper)]
+
+	def _open_writer(self, file, file2, **kwargs):
+		# TODO backwards-incompatible change (?) would be to use outfiles.interleaved
+		# for all outputs
+		return seqio.open(file, file2, mode='w', qualities=self.uses_qualities,
+			colorspace=self._colorspace, **kwargs)
+
+	# TODO set max_n default to None
+	def set_output(self, outfiles, minimum_length=0, maximum_length=sys.maxsize,
+			max_n=-1, discard_trimmed=False, discard_untrimmed=False):
+		self._filters = []
+		self._outfiles = outfiles
+		filter_wrapper = self._filter_wrapper()
+
+		if outfiles.rest:
+			self._filters.append(RestFileWriter(outfiles.rest))
+		if outfiles.info:
+			self._filters.append(InfoFileWriter(outfiles.info))
+		if outfiles.wildcard:
+			self._filters.append(WildcardFileWriter(outfiles.wildcard))
+
+		too_short_writer = None
+		if minimum_length > 0:
+			if outfiles.too_short:
+				too_short_writer = self._open_writer(outfiles.too_short, outfiles.too_short2)
+			self._filters.append(
+				filter_wrapper(too_short_writer, TooShortReadFilter(minimum_length)))
+
+		too_long_writer = None
+		if maximum_length < sys.maxsize:
+			if outfiles.too_long:
+				too_long_writer = self._open_writer(outfiles.too_long, outfiles.too_long2)
+			self._filters.append(
+				filter_wrapper(too_long_writer, TooLongReadFilter(maximum_length)))
+
+		if max_n != -1:
+			self._filters.append(filter_wrapper(None, NContentFilter(max_n)))
+
+		if int(discard_trimmed) + int(discard_untrimmed) + int(outfiles.untrimmed is not None) > 1:
+			raise ValueError('discard_trimmed, discard_untrimmed and outfiles.untrimmed must not '
+				'be set simultaneously')
+
+		if outfiles.demultiplex:
+			self._demultiplexer = self._create_demultiplexer(outfiles)
+			self._filters.append(self._demultiplexer)
+		else:
+			# Set up the remaining filters to deal with --discard-trimmed,
+			# --discard-untrimmed and --untrimmed-output. These options
+			# are mutually exclusive in order to avoid brain damage.
+			if discard_trimmed:
+				self._filters.append(filter_wrapper(None, DiscardTrimmedFilter()))
+			elif discard_untrimmed:
+				self._filters.append(filter_wrapper(None, DiscardUntrimmedFilter()))
+			elif outfiles.untrimmed:
+				untrimmed_writer = self._open_writer(outfiles.untrimmed, outfiles.untrimmed2)
+				self._filters.append(filter_wrapper(untrimmed_writer, DiscardUntrimmedFilter()))
+			self._filters.append(self._final_filter(outfiles))
+
+	def close(self):
+		for f in self._outfiles:
+			# TODO do not use hasattr
+			if f is not None and f is not sys.stdin and f is not sys.stdout and hasattr(f, 'close'):
+				f.close()
+		if self._demultiplexer is not None:
+			self._demultiplexer.close()
+
+	@property
+	def uses_qualities(self):
+		return self._reader.delivers_qualities
+
+	def run(self):
+		(n, total1_bp, total2_bp) = self.process_reads()
+		# TODO
+		m = self._modifiers
+		m2 = getattr(self, '_modifiers2', [])
+		stats = Statistics()
+		stats.collect(n, total1_bp, total2_bp, m, m2, self._filters)
+		return stats
+
+	def process_reads(self):
+		raise NotImplementedError()
+
+	def _filter_wrapper(self):
+		raise NotImplementedError()
+
+	def _final_filter(self, outfiles):
+		raise NotImplementedError()
+
+	def _create_demultiplexer(self, outfiles):
+		raise NotImplementedError()
+
+
+class SingleEndPipeline(Pipeline):
+	"""
+	Processing pipeline for single-end reads
+	"""
+	paired = False
+
+	def __init__(self):
+		super(SingleEndPipeline, self).__init__()
+		self._modifiers = []
+
+	def add(self, modifier):
+		self._modifiers.append(modifier)
+
+	def add1(self, modifier):
+		"""An alias for the add() function. Makes the interface similar to PairedEndPipeline"""
+		self.add(modifier)
+
+	def process_reads(self):
+		"""Run the pipeline. Return statistics"""
+		n = 0  # no. of processed reads  # TODO turn into attribute
+		total_bp = 0
+		for read in self._reader:
+			n += 1
+			total_bp += len(read.sequence)
+			for modifier in self._modifiers:
+				read = modifier(read)
+			for filter in self._filters:
+				if filter(read):
+					break
+		return (n, total_bp, None)
+
+	def _filter_wrapper(self):
+		return Redirector
+
+	def _final_filter(self, outfiles):
+		writer = self._open_writer(outfiles.out, outfiles.out2)
+		return NoFilter(writer)
+
+	def _create_demultiplexer(self, outfiles):
+		return Demultiplexer(outfiles.out, outfiles.untrimmed, qualities=self.uses_qualities,
+			colorspace=self._colorspace)
+
+
+class PairedEndPipeline(Pipeline):
+	"""
+	Processing pipeline for paired-end reads.
+	"""
+
+	def __init__(self, pair_filter_mode, modify_first_read_only=False):
+		"""Setting modify_first_read_only to True enables "legacy mode"
+		"""
+		super(PairedEndPipeline, self).__init__()
+		self._modifiers2 = []
+		self._pair_filter_mode = pair_filter_mode
+		self._modify_first_read_only = modify_first_read_only
+		self._add_both_called = False
+		self._should_warn_legacy = False
+		self._reader = None
+
+	def set_input(self, *args, **kwargs):
+		super(PairedEndPipeline, self).set_input(*args, **kwargs)
+		if not self._reader.delivers_qualities:
+			self._modifiers2 = [m for m in self._modifiers2 if not isinstance(m, ZeroCapper)]
+
+	def add(self, modifier):
+		"""
+		Add a modifier for R1 and R2. If modify_first_read_only is True,
+		the modifier is *not* added for R2.
+		"""
+		self._modifiers.append(modifier)
+		if not self._modify_first_read_only:
+			self._modifiers2.append(modifier)
+		else:
+			self._should_warn_legacy = True
+
+	def add1(self, modifier):
+		"""Add a modifier for R1 only"""
+		self._modifiers.append(modifier)
+
+	def add2(self, modifier):
+		"""Add a modifier for R2 only"""
+		assert not self._modify_first_read_only
+		self._modifiers2.append(modifier)
+
+	def process_reads(self):
+		n = 0  # no. of processed reads
+		total1_bp = 0
+		total2_bp = 0
+		for read1, read2 in self._reader:
+			n += 1
+			total1_bp += len(read1.sequence)
+			total2_bp += len(read2.sequence)
+			for modifier in self._modifiers:
+				read1 = modifier(read1)
+			for modifier in self._modifiers2:
+				read2 = modifier(read2)
+			for filter in self._filters:
+				# Stop writing as soon as one of the filters was successful.
+				if filter(read1, read2):
+					break
+		return (n, total1_bp, total2_bp)
+
+	@property
+	def should_warn_legacy(self):
+		return self._should_warn_legacy
+
+	@should_warn_legacy.setter
+	def should_warn_legacy(self, value):
+		self._should_warn_legacy = bool(value)
+
+	@property
+	def paired(self):
+		return 'first' if self._modify_first_read_only else 'both'
+
+	def _filter_wrapper(self):
+		return functools.partial(PairedRedirector, pair_filter_mode=self._pair_filter_mode)
+
+	def _final_filter(self, outfiles):
+		writer = self._open_writer(outfiles.out, outfiles.out2, interleaved=outfiles.interleaved)
+		return PairedNoFilter(writer)
+
+	def _create_demultiplexer(self, outfiles):
+		return PairedEndDemultiplexer(outfiles.out, outfiles.out2,
+			outfiles.untrimmed, outfiles.untrimmed2, qualities=self.uses_qualities,
+			colorspace=self._colorspace)
+
+
+def available_cpu_count():
+	"""
+	Return the number of available virtual or physical CPUs on this system.
+	The number of available CPUs can be smaller than the total number of CPUs
+	when the cpuset(7) mechanism is in use, as is the case on some cluster
+	systems.
+
+	Adapted from http://stackoverflow.com/a/1006301/715090
+	"""
+	try:
+		with open('/proc/self/status') as f:
+			status = f.read()
+		m = re.search(r'(?m)^Cpus_allowed:\s*(.*)$', status)
+		if m:
+			res = bin(int(m.group(1).replace(',', ''), 16)).count('1')
+			if res > 0:
+				return min(res, multiprocessing.cpu_count())
+	except IOError:
+		pass
+
+	return multiprocessing.cpu_count()
+
+
+def reader_process(file, file2, connections, queue, buffer_size, stdin_fd):
+	"""
+	Read chunks of FASTA or FASTQ data from *file* and send to a worker.
+
+	queue -- a Queue of worker indices. A worker writes its own index into this
+		queue to notify the reader that it is ready to receive more data.
+	connections -- a list of Connection objects, one for each worker.
+
+	The function repeatedly
+
+	- reads a chunk from the file
+	- reads a worker index from the Queue
+	- sends the chunk to connections[index]
+
+	and finally sends "poison pills" (the value -1) to all connections.
+	"""
+	if stdin_fd != -1:
+		sys.stdin = os.fdopen(stdin_fd)
+	try:
+		with xopen(file, 'rb') as f:
+			if file2:
+				with xopen(file2, 'rb') as f2:
+					for chunk_index, (chunk1, chunk2) in enumerate(read_paired_chunks(f, f2, buffer_size)):
+						# Determine the worker that should get this chunk
+						worker_index = queue.get()
+						pipe = connections[worker_index]
+						pipe.send(chunk_index)
+						pipe.send_bytes(chunk1)
+						pipe.send_bytes(chunk2)
+			else:
+				for chunk_index, chunk in enumerate(read_chunks_from_file(f, buffer_size)):
+					# Determine the worker that should get this chunk
+					worker_index = queue.get()
+					pipe = connections[worker_index]
+					pipe.send(chunk_index)
+					pipe.send_bytes(chunk)
+
+		# Send poison pills to all workers
+		for _ in range(len(connections)):
+			worker_index = queue.get()
+			connections[worker_index].send(-1)
+	except Exception as e:
+		# TODO better send this to a common "something went wrong" Queue
+		for worker_index in range(len(connections)):
+			connections[worker_index].send(-2)
+			connections[worker_index].send((e, traceback.format_exc()))
+
+
+class WorkerProcess(Process):
+	"""
+	The worker repeatedly reads chunks of data from the read_pipe, runs the pipeline on it
+	and sends the processed chunks to the write_pipe.
+
+	To notify the reader process that it wants data, it puts its own identifier into the
+	need_work_queue before attempting to read data from the read_pipe.
+	"""
+	def __init__(self, id_, pipeline, filtering_options, input_path1, input_path2,
+			interleaved_input, orig_outfiles, read_pipe, write_pipe, need_work_queue):
+		super(WorkerProcess, self).__init__()
+		self._id = id_
+		self._pipeline = pipeline
+		self._filtering_options = filtering_options
+		self._input_path1 = input_path1
+		self._input_path2 = input_path2
+		self._interleaved_input = interleaved_input
+		self._orig_outfiles = orig_outfiles
+		self._read_pipe = read_pipe
+		self._write_pipe = write_pipe
+		self._need_work_queue = need_work_queue
+
+	def run(self):
+		try:
+			stats = Statistics()
+			while True:
+				# Notify reader that we need data
+				self._need_work_queue.put(self._id)
+				chunk_index = self._read_pipe.recv()
+				if chunk_index == -1:
+					# reader is done
+					break
+				elif chunk_index == -2:
+					# An exception has occurred in the reader
+					e, tb_str = self._read_pipe.recv()
+					logger.error('%s', tb_str)
+					raise e
+
+				# Setting the .buffer.name attributess below is necessary because
+				# file format detection uses the file name
+				data = self._read_pipe.recv_bytes()
+				input = io.TextIOWrapper(io.BytesIO(data), encoding='ascii')
+				input.buffer.name = self._input_path1
+
+				if self._input_path2:
+					data = self._read_pipe.recv_bytes()
+					input2 = io.TextIOWrapper(io.BytesIO(data), encoding='ascii')
+					input2.buffer.name = self._input_path2
+				else:
+					input2 = None
+				output = io.TextIOWrapper(io.BytesIO(), encoding='ascii')
+				output.buffer.name = self._orig_outfiles.out.name
+
+				if self._orig_outfiles.out2 is not None:
+					output2 = io.TextIOWrapper(io.BytesIO(), encoding='ascii')
+					output2.buffer.name = self._orig_outfiles.out2.name
+				else:
+					output2 = None
+
+				outfiles = OutputFiles(out=output, out2=output2, interleaved=self._orig_outfiles.interleaved)
+				self._pipeline.set_input(input, input2, interleaved=self._interleaved_input)
+				self._pipeline.set_output(outfiles, *self._filtering_options[0], **self._filtering_options[1])
+				(n, bp1, bp2) = self._pipeline.process_reads()
+				cur_stats = Statistics()
+				cur_stats.collect(n, bp1, bp2, [], [], self._pipeline._filters)
+				stats += cur_stats
+
+				output.flush()
+				processed_chunk = output.buffer.getvalue()
+
+				self._write_pipe.send(chunk_index)
+				self._write_pipe.send_bytes(processed_chunk)
+				if self._orig_outfiles.out2 is not None:
+					output2.flush()
+					processed_chunk2 = output2.buffer.getvalue()
+					self._write_pipe.send_bytes(processed_chunk2)
+
+			m = self._pipeline._modifiers
+			m2 = getattr(self._pipeline, '_modifiers2', [])
+			modifier_stats = Statistics()
+			modifier_stats.collect(0, 0, 0 if self._pipeline.paired else None, m, m2, [])
+			stats += modifier_stats
+			self._write_pipe.send(-1)
+			self._write_pipe.send(stats)
+		except Exception as e:
+			self._write_pipe.send(-2)
+			self._write_pipe.send((e, traceback.format_exc()))
+
+
+class OrderedChunkWriter(object):
+	"""
+	We may receive chunks of processed data from worker processes
+	in any order. This class writes them to an output file in
+	the correct order.
+	"""
+	def __init__(self, outfile):
+		self._chunks = dict()
+		self._current_index = 0
+		self._outfile = outfile
+
+	def write(self, data, chunk_index):
+		"""
+		"""
+		self._chunks[chunk_index] = data
+		while self._current_index in self._chunks:
+			# TODO 1) do not decode 2) use .buffer.write
+			self._outfile.write(self._chunks[self._current_index].decode('utf-8'))
+			del self._chunks[self._current_index]
+			self._current_index += 1
+
+	def wrote_everything(self):
+		return not self._chunks
+
+
+class ParallelPipelineRunner(object):
+	"""
+	Wrap a SingleEndPipeline, running it in parallel
+
+	- When set_input() is called, a reader process is spawned.
+	- When run() is called, as many worker processes as requested are spawned.
+	- In the main process, results are written to the output files in the correct
+	  order, and statistics are aggregated.
+
+	If a worker needs work, it puts its own index into a Queue() (_need_work_queue).
+	The reader process listens on this queue and sends the raw data to the
+	worker that has requested work. For sending the data from reader to worker,
+	a Connection() is used. There is one such connection for each worker (self._pipes).
+
+	For sending the processed data from the worker to the main process, there
+	is a second set of connections, again one for each worker.
+
+	When the reader is finished, it sends 'poison pills' to all workers.
+	When a worker receives this, it sends a poison pill to the main process,
+	followed by a Statistics object that contains statistics about all the reads
+	processed by that worker.
+	"""
+
+	def __init__(self, pipeline, n_workers, buffer_size=4*1024**2):
+		self._pipeline = pipeline
+		self._pipes = []  # the workers read from these
+		self._reader_process = None
+		self._filtering_options = None
+		self._outfiles = None
+		self._input_path1 = None
+		self._input_path2 = None
+		self._interleaved_input = None
+		self._n_workers = n_workers
+		self._need_work_queue = Queue()
+		self._buffer_size = buffer_size
+
+	def set_input(self, file1, file2=None, qualfile=None, colorspace=False, fileformat=None,
+			interleaved=False):
+		if self._reader_process is not None:
+			raise RuntimeError('Do not call set_input more than once')
+		assert qualfile is None and colorspace is False and fileformat is None
+		self._input_path1 = file1 if type(file1) is str else file1.name
+		self._input_path2 = file2 if type(file2) is str or file2 is None else file2.name
+		self._interleaved_input = interleaved
+		connections = [Pipe(duplex=False) for _ in range(self._n_workers)]
+		self._pipes, connw = zip(*connections)
+		try:
+			fileno = sys.stdin.fileno()
+		except io.UnsupportedOperation:
+			# This happens during tests: pytest sets sys.stdin to an object
+			# that does not have a file descriptor.
+			fileno = -1
+		self._reader_process = Process(target=reader_process, args=(file1, file2, connw,
+			self._need_work_queue, self._buffer_size, fileno))
+		self._reader_process.daemon = True
+		self._reader_process.start()
+
+	@staticmethod
+	def can_output_to(outfiles):
+		return (
+			outfiles.out is not None
+			and outfiles.rest is None
+			and outfiles.info is None
+			and outfiles.wildcard is None
+			and outfiles.too_short is None
+			and outfiles.too_short2 is None
+			and outfiles.too_long is None
+			and outfiles.too_long2 is None
+			and outfiles.untrimmed is None
+			and outfiles.untrimmed2 is None
+			and not outfiles.demultiplex
+		)
+
+	def set_output(self, outfiles, *args, **kwargs):
+		if not self.can_output_to(outfiles):
+			raise ValueError()
+		self._filtering_options = args, kwargs
+		self._outfiles = outfiles
+
+	def _start_workers(self):
+		workers = []
+		connections = []
+		for index in range(self._n_workers):
+			conn_r, conn_w = Pipe(duplex=False)
+			connections.append(conn_r)
+			worker = WorkerProcess(index, self._pipeline,
+				self._filtering_options, self._input_path1, self._input_path2,
+				self._interleaved_input, self._outfiles,
+				self._pipes[index], conn_w, self._need_work_queue)
+			worker.daemon = True
+			worker.start()
+			workers.append(worker)
+		return workers, connections
+
+	def run(self):
+		workers, connections = self._start_workers()
+		writers = []
+		for outfile in [self._outfiles.out, self._outfiles.out2]:
+			if outfile is None:
+				continue
+			writers.append(OrderedChunkWriter(outfile))
+		stats = None
+		while connections:
+			ready_connections = multiprocessing.connection.wait(connections)
+			for connection in ready_connections:
+				chunk_index = connection.recv()
+				if chunk_index == -1:
+					# the worker is done
+					cur_stats = connection.recv()
+					if stats == -2:
+						# An exception has occurred in the worker (see below,
+						# this happens only when there is an exception sending
+						# the statistics)
+						e, tb_str = connection.recv()
+						# TODO traceback should only be printed in development
+						logger.error('%s', tb_str)
+						raise e
+					if stats is None:
+						stats = cur_stats
+					else:
+						stats += cur_stats
+					connections.remove(connection)
+					continue
+				elif chunk_index == -2:
+					# An exception has occurred in the worker
+					e, tb_str = connection.recv()
+					logger.error('%s', tb_str)
+					# We should use the worker's actual traceback object
+					# here, but traceback objects are not picklable.
+					raise e
+
+				for writer in writers:
+					data = connection.recv_bytes()
+					writer.write(data, chunk_index)
+		for writer in writers:
+			assert writer.wrote_everything()
+		for w in workers:
+			w.join()
+		self._reader_process.join()
+		return stats
+
+	def close(self):
+		for f in self._outfiles:
+			# TODO do not use hasattr
+			if f is not None and f is not sys.stdin and f is not sys.stdout and hasattr(f, 'close'):
+				f.close()
diff --git a/cutadapt/qualtrim.py b/src/cutadapt/qualtrim.py
similarity index 100%
rename from cutadapt/qualtrim.py
rename to src/cutadapt/qualtrim.py
diff --git a/cutadapt/report.py b/src/cutadapt/report.py
similarity index 50%
rename from cutadapt/report.py
rename to src/cutadapt/report.py
index c3c0e1a..9e1cb36 100644
--- a/cutadapt/report.py
+++ b/src/cutadapt/report.py
@@ -10,7 +10,8 @@ import textwrap
 from .adapters import BACK, FRONT, PREFIX, SUFFIX, ANYWHERE, LINKED
 from .modifiers import QualityTrimmer, AdapterCutter
 from .filters import (NoFilter, PairedNoFilter, TooShortReadFilter, TooLongReadFilter,
-	DiscardTrimmedFilter, DiscardUntrimmedFilter, Demultiplexer, NContentFilter)
+	DiscardTrimmedFilter, DiscardUntrimmedFilter, PairedEndDemultiplexer, Demultiplexer,
+	NContentFilter, InfoFileWriter, WildcardFileWriter, RestFileWriter)
 
 
 def safe_divide(numerator, denominator):
@@ -24,55 +25,75 @@ class Statistics:
 	def __init__(self):
 		"""
 		"""
-		self.n = 0
-		self.total_bp = 0
-		self.total_bp1 = 0
-		self.total_bp2 = 0
-		self.paired = False
-		self.time = 0.01  # CPU time in seconds
+		self.paired = None
 		self.too_short = None
 		self.too_long = None
+		self.too_many_n = None
+		self.did_quality_trimming = None
+		self.n = 0
 		self.written = 0
+		self.total_bp = [0, 0]
 		self.written_bp = [0, 0]
-		self.too_many_n = None
 		self.with_adapters = [0, 0]
 		self.quality_trimmed_bp = [0, 0]
-		self.did_quality_trimming = False
-		self.quality_trimmed = 0
-		self.adapter_stats = [None, None]
-		self.adapter_lists = [[], []]
-
-		# These attributes are derived from the ones above
-		self.total_written_bp = 0
-		self.too_short_fraction = 0
-		self.too_long_fraction = 0
-		self.total_written_bp_fraction = 0
-		self.with_adapters_fraction = []
-		self.written_fraction = 0
-		self.quality_trimmed_fraction = 0
-		self.too_many_n_fraction = None
-
-	def collect(self, n, total_bp1, total_bp2, time, modifiers, modifiers2, writers):
+		self.adapter_stats = [[], []]
+
+	def __iadd__(self, other):
+		self.n += other.n
+		self.written += other.written
+
+		if self.paired is None:
+			self.paired = other.paired
+		elif self.paired != other.paired:
+			raise ValueError('Incompatible Statistics: paired is not equal')
+		if self.did_quality_trimming is None:
+			self.did_quality_trimming = other.did_quality_trimming
+		elif self.did_quality_trimming != other.did_quality_trimming:
+			raise ValueError('Incompatible Statistics: did_quality_trimming is not equal')
+
+		def add_if_not_none(a, b):
+			if a is None:
+				return b
+			if b is None:
+				return a
+			return a + b
+		self.too_short = add_if_not_none(self.too_short, other.too_short)
+		self.too_long = add_if_not_none(self.too_long, other.too_long)
+		self.too_many_n = add_if_not_none(self.too_many_n, other.too_many_n)
+		for i in (0, 1):
+			self.total_bp[i] += other.total_bp[i]
+			self.written_bp[i] += other.written_bp[i]
+			self.with_adapters[i] += other.with_adapters[i]
+			self.quality_trimmed_bp[i] += other.quality_trimmed_bp[i]
+			if self.adapter_stats[i] and other.adapter_stats[i]:
+				if len(self.adapter_stats[i]) != len(other.adapter_stats[i]):
+					raise ValueError('Incompatible Statistics objects (adapter_stats length)')
+				for j in range(len(self.adapter_stats[i])):
+					self.adapter_stats[i][j] += other.adapter_stats[i][j]
+			elif other.adapter_stats[i]:
+				assert self.adapter_stats[i] == []
+				self.adapter_stats[i] = other.adapter_stats[i]
+		return self
+
+	def collect(self, n, total_bp1, total_bp2, modifiers, modifiers2, writers):
 		"""
 		n -- total number of reads
 		total_bp1 -- number of bases in first reads
 		total_bp2 -- number of bases in second reads. None for single-end data.
-		time -- CPU time
 		"""
 		self.n = n
-		self.total_bp = total_bp1
-		self.total_bp1 = total_bp1
+		self.total_bp[0] = total_bp1
 		if total_bp2 is None:
 			self.paired = False
 		else:
 			self.paired = True
-			self.total_bp2 = total_bp2
-			self.total_bp += total_bp2
-		self.time = max(time, 0.01)
+			self.total_bp[1] = total_bp2
 
 		# Collect statistics from writers/filters
 		for w in writers:
-			if isinstance(w, (NoFilter, PairedNoFilter, Demultiplexer)) or \
+			if isinstance(w, (InfoFileWriter, RestFileWriter, WildcardFileWriter)):
+				pass
+			elif isinstance(w, (NoFilter, PairedNoFilter, PairedEndDemultiplexer, Demultiplexer)) or \
 					isinstance(w.filter, (DiscardTrimmedFilter, DiscardUntrimmedFilter)):
 				self.written += w.written
 				self.written_bp[0] += w.written_bp[0]
@@ -93,19 +114,47 @@ class Statistics:
 					self.did_quality_trimming = True
 				elif isinstance(modifier, AdapterCutter):
 					self.with_adapters[i] += modifier.with_adapters
-					self.adapter_stats[i] = modifier.adapter_statistics
-					self.adapter_lists[i] = modifier.adapters
+					self.adapter_stats[i] = list(modifier.adapter_statistics.values())
+
+	@property
+	def total(self):
+		return sum(self.total_bp)
+
+	@property
+	def quality_trimmed(self):
+		return sum(self.quality_trimmed_bp)
+
+	@property
+	def total_written_bp(self):
+		return sum(self.written_bp)
 
-		# Set the attributes that are derived from the other ones
-		self.quality_trimmed = sum(self.quality_trimmed_bp)
-		self.total_written_bp = sum(self.written_bp)
-		self.written_fraction = safe_divide(self.written, self.n)
-		self.with_adapters_fraction = [safe_divide(v, self.n) for v in self.with_adapters]
-		self.quality_trimmed_fraction = safe_divide(self.quality_trimmed, self.total_bp)
-		self.total_written_bp_fraction = safe_divide(self.total_written_bp, self.total_bp)
-		self.too_short_fraction = safe_divide(self.too_short, self.n)
-		self.too_long_fraction = safe_divide(self.too_long, self.n)
-		self.too_many_n_fraction = safe_divide(self.too_many_n, self.n)
+	@property
+	def written_fraction(self):
+		return safe_divide(self.written, self.n)
+
+	@property
+	def with_adapters_fraction(self):
+		return [safe_divide(v, self.n) for v in self.with_adapters]
+
+	@property
+	def quality_trimmed_fraction(self):
+		return safe_divide(self.quality_trimmed, self.total)
+
+	@property
+	def total_written_bp_fraction(self):
+		return safe_divide(self.total_written_bp, self.total)
+
+	@property
+	def too_short_fraction(self):
+		return safe_divide(self.too_short, self.n)
+
+	@property
+	def too_long_fraction(self):
+		return safe_divide(self.too_long, self.n)
+
+	@property
+	def too_many_n_fraction(self):
+		return safe_divide(self.too_many_n, self.n)
 
 
 ADAPTER_TYPES = {
@@ -132,31 +181,24 @@ def print_error_ranges(adapter_length, error_rate):
 	print()
 
 
-def print_histogram(adapter_statistics, where, adapter, n, gc_content):
+def print_histogram(end_statistics, n, gc_content):
 	"""
 	Print a histogram. Also, print the no. of reads expected to be
 	trimmed by chance (assuming a uniform distribution of nucleotides in the reads).
 
-	adapter_statistics -- AdapterStatistics object
-	where -- 'front' or 'back'
+	adapter_statistics -- EndStatistics object
 	adapter_length -- adapter length
 	n -- total no. of reads.
 	"""
-	if where not in ('front', 'back'):
-		assert ValueError('where must be "front" or "back"')
-	if where == 'front':
-		d = adapter_statistics.lengths_front
-		errors = adapter_statistics.errors_front
-	else:
-		d = adapter_statistics.lengths_back
-		errors = adapter_statistics.errors_back
+	d = end_statistics.lengths
+	errors = end_statistics.errors
 
-	match_probabilities = adapter.random_match_probabilities(gc_content=gc_content)
+	match_probabilities = end_statistics.random_match_probabilities(gc_content=gc_content)
 	print("length", "count", "expect", "max.err", "error counts", sep="\t")
 	for length in sorted(d):
 		# when length surpasses adapter_length, the
 		# probability does not increase anymore
-		expect = n * match_probabilities[min(len(adapter), length)]
+		expect = n * match_probabilities[min(len(end_statistics.sequence), length)]
 		count = d[length]
 		max_errors = max(errors[length].keys())
 		errs = ' '.join(str(errors[length][e]) for e in range(max_errors+1))
@@ -164,7 +206,7 @@ def print_histogram(adapter_statistics, where, adapter, n, gc_content):
 			length,
 			count,
 			"{0:.1F}".format(expect),
-			int(adapter.max_error_rate*min(length, len(adapter))),
+			int(end_statistics.max_error_rate*min(length, len(end_statistics.sequence))),
 			errs,
 			sep="\t")
 	print()
@@ -211,69 +253,63 @@ def redirect_standard_output(file):
 	sys.stdout = old_stdout
 
 
-def print_report(stats, gc_content):
+def print_report(stats, time, gc_content):
 	"""Print report to standard output."""
 	if stats.n == 0:
 		print("No reads processed! Either your input file is empty or you used the wrong -f/--format parameter.")
 		return
 	print("Finished in {0:.2F} s ({1:.0F} us/read; {2:.2F} M reads/minute).".format(
-		stats.time, 1E6 * stats.time / stats.n, stats.n / stats.time * 60 / 1E6))
+		time, 1E6 * time / stats.n, stats.n / time * 60 / 1E6))
 
 	report = "\n=== Summary ===\n\n"
 	if stats.paired:
 		report += textwrap.dedent("""\
-		Total read pairs processed:      {n:13,d}
-		  Read 1 with adapter:           {with_adapters[0]:13,d} ({with_adapters_fraction[0]:.1%})
-		  Read 2 with adapter:           {with_adapters[1]:13,d} ({with_adapters_fraction[1]:.1%})
+		Total read pairs processed:      {o.n:13,d}
+		  Read 1 with adapter:           {o.with_adapters[0]:13,d} ({o.with_adapters_fraction[0]:.1%})
+		  Read 2 with adapter:           {o.with_adapters[1]:13,d} ({o.with_adapters_fraction[1]:.1%})
 		""")
 	else:
 		report += textwrap.dedent("""\
-		Total reads processed:           {n:13,d}
-		Reads with adapters:             {with_adapters[0]:13,d} ({with_adapters_fraction[0]:.1%})
+		Total reads processed:           {o.n:13,d}
+		Reads with adapters:             {o.with_adapters[0]:13,d} ({o.with_adapters_fraction[0]:.1%})
 		""")
 	if stats.too_short is not None:
-		report += "{pairs_or_reads} that were too short:       {too_short:13,d} ({too_short_fraction:.1%})\n"
+		report += "{pairs_or_reads} that were too short:       {o.too_short:13,d} ({o.too_short_fraction:.1%})\n"
 	if stats.too_long is not None:
-		report += "{pairs_or_reads} that were too long:        {too_long:13,d} ({too_long_fraction:.1%})\n"
+		report += "{pairs_or_reads} that were too long:        {o.too_long:13,d} ({o.too_long_fraction:.1%})\n"
 	if stats.too_many_n is not None:
-		report += "{pairs_or_reads} with too many N:           {too_many_n:13,d} ({too_many_n_fraction:.1%})\n"
+		report += "{pairs_or_reads} with too many N:           {o.too_many_n:13,d} ({o.too_many_n_fraction:.1%})\n"
 
 	report += textwrap.dedent("""\
-	{pairs_or_reads} written (passing filters): {written:13,d} ({written_fraction:.1%})
+	{pairs_or_reads} written (passing filters): {o.written:13,d} ({o.written_fraction:.1%})
 
-	Total basepairs processed: {total_bp:13,d} bp
+	Total basepairs processed: {o.total:13,d} bp
 	""")
 	if stats.paired:
-		report += "  Read 1: {total_bp1:13,d} bp\n"
-		report += "  Read 2: {total_bp2:13,d} bp\n"
+		report += "  Read 1: {o.total_bp[0]:13,d} bp\n"
+		report += "  Read 2: {o.total_bp[1]:13,d} bp\n"
 
 	if stats.did_quality_trimming:
-		report += "Quality-trimmed:           {quality_trimmed:13,d} bp ({quality_trimmed_fraction:.1%})\n"
+		report += "Quality-trimmed:           {o.quality_trimmed:13,d} bp ({o.quality_trimmed_fraction:.1%})\n"
 		if stats.paired:
-			report += "  Read 1: {quality_trimmed_bp[0]:13,d} bp\n"
-			report += "  Read 2: {quality_trimmed_bp[1]:13,d} bp\n"
+			report += "  Read 1: {o.quality_trimmed_bp[0]:13,d} bp\n"
+			report += "  Read 2: {o.quality_trimmed_bp[1]:13,d} bp\n"
 
-	report += "Total written (filtered):  {total_written_bp:13,d} bp ({total_written_bp_fraction:.1%})\n"
+	report += "Total written (filtered):  {o.total_written_bp:13,d} bp ({o.total_written_bp_fraction:.1%})\n"
 	if stats.paired:
-		report += "  Read 1: {written_bp[0]:13,d} bp\n"
-		report += "  Read 2: {written_bp[1]:13,d} bp\n"
-	v = vars(stats)
-	v['pairs_or_reads'] = "Pairs" if stats.paired else "Reads"
-	try:
-		report = report.format(**v)
-	except ValueError:
-		# Python 2.6 does not support the comma format specifier (PEP 378)
-		report = report.replace(",d}", "d}").format(**v)
+		report += "  Read 1: {o.written_bp[0]:13,d} bp\n"
+		report += "  Read 2: {o.written_bp[1]:13,d} bp\n"
+	pairs_or_reads = "Pairs" if stats.paired else "Reads"
+	report = report.format(o=stats, pairs_or_reads=pairs_or_reads)
 	print(report)
 
 	warning = False
 	for which_in_pair in (0, 1):
-		for adapter in stats.adapter_lists[which_in_pair]:
-			adapter_statistics = stats.adapter_stats[which_in_pair][adapter]
-			total_front = sum(adapter_statistics.lengths_front.values())
-			total_back = sum(adapter_statistics.lengths_back.values())
+		for adapter_statistics in stats.adapter_stats[which_in_pair]:
+			total_front = sum(adapter_statistics.front.lengths.values())
+			total_back = sum(adapter_statistics.back.lengths.values())
 			total = total_front + total_back
-			where = adapter.where
+			where = adapter_statistics.where
 			assert where in (ANYWHERE, LINKED) or (where in (BACK, SUFFIX) and total_front == 0) or (where in (FRONT, PREFIX) and total_back == 0)
 
 			if stats.paired:
@@ -281,21 +317,21 @@ def print_report(stats, gc_content):
 			else:
 				extra = ''
 
-			print("=" * 3, extra + "Adapter", adapter.name, "=" * 3)
+			print("=" * 3, extra + "Adapter", adapter_statistics.name, "=" * 3)
 			print()
 
 			if where == LINKED:
 				print("Sequence: {0}...{1}; Type: linked; Length: {2}+{3}; "
 					"5' trimmed: {4} times; 3' trimmed: {5} times".format(
-						adapter.front_adapter.sequence,
-						adapter.back_adapter.sequence,
-						len(adapter.front_adapter.sequence),
-						len(adapter.back_adapter.sequence),
+						adapter_statistics.front.sequence,
+						adapter_statistics.back.sequence,
+						len(adapter_statistics.front.sequence),
+						len(adapter_statistics.back.sequence),
 						total_front, total_back))
 			else:
 				print("Sequence: {0}; Type: {1}; Length: {2}; Trimmed: {3} times.".
-					format(adapter.sequence, ADAPTER_TYPES[adapter.where],
-						len(adapter.sequence), total))
+					format(adapter_statistics.front.sequence, ADAPTER_TYPES[adapter_statistics.where],
+						len(adapter_statistics.front.sequence), total))
 			if total == 0:
 				print()
 				continue
@@ -303,35 +339,33 @@ def print_report(stats, gc_content):
 				print(total_front, "times, it overlapped the 5' end of a read")
 				print(total_back, "times, it overlapped the 3' end or was within the read")
 				print()
-				print_error_ranges(len(adapter), adapter.max_error_rate)
+				print_error_ranges(len(adapter_statistics.front.sequence), adapter_statistics.front.max_error_rate)
 				print("Overview of removed sequences (5')")
-				print_histogram(adapter_statistics, 'front', adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.front, stats.n, gc_content)
 				print()
 				print("Overview of removed sequences (3' or within)")
-				print_histogram(adapter_statistics, 'back', adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.back, stats.n, gc_content)
 			elif where == LINKED:
 				print()
-				print_error_ranges(len(adapter.front_adapter), adapter.front_adapter.max_error_rate)
-				print_error_ranges(len(adapter.back_adapter), adapter.back_adapter.max_error_rate)
+				print_error_ranges(len(adapter_statistics.front.sequence), adapter_statistics.front.max_error_rate)
+				print_error_ranges(len(adapter_statistics.back.sequence), adapter_statistics.back.max_error_rate)
 				print("Overview of removed sequences at 5' end")
-				print_histogram(adapter_statistics, 'front',
-					adapter.front_adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.front, stats.n, gc_content)
 				print()
 				print("Overview of removed sequences at 3' end")
-				print_histogram(adapter_statistics, 'back',
-					adapter.back_adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.back, stats.n, gc_content)
 			elif where in (FRONT, PREFIX):
 				print()
-				print_error_ranges(len(adapter), adapter.max_error_rate)
+				print_error_ranges(len(adapter_statistics.front.sequence), adapter_statistics.front.max_error_rate)
 				print("Overview of removed sequences")
-				print_histogram(adapter_statistics, 'front', adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.front, stats.n, gc_content)
 			else:
 				assert where in (BACK, SUFFIX)
 				print()
-				print_error_ranges(len(adapter), adapter.max_error_rate)
-				warning = warning or print_adjacent_bases(adapter_statistics.adjacent_bases)
+				print_error_ranges(len(adapter_statistics.back.sequence), adapter_statistics.back.max_error_rate)
+				warning = warning or print_adjacent_bases(adapter_statistics.back.adjacent_bases)
 				print("Overview of removed sequences")
-				print_histogram(adapter_statistics, 'back', adapter, stats.n, gc_content)
+				print_histogram(adapter_statistics.back, stats.n, gc_content)
 
 	if warning:
 		print('WARNING:')
diff --git a/cutadapt/seqio.py b/src/cutadapt/seqio.py
similarity index 80%
rename from cutadapt/seqio.py
rename to src/cutadapt/seqio.py
index 191bf98..9162aa6 100644
--- a/cutadapt/seqio.py
+++ b/src/cutadapt/seqio.py
@@ -81,6 +81,7 @@ class Sequence(object):
 class SequenceReader(object):
 	"""Read possibly compressed files containing sequences"""
 	_close_on_exit = False
+	paired = False
 
 	def __init__(self, file):
 		"""
@@ -193,6 +194,7 @@ class FastaReader(SequenceReader):
 	"""
 	Reader for FASTA files.
 	"""
+
 	def __init__(self, file, keep_linebreaks=False, sequence_class=Sequence):
 		"""
 		file is a path or a file-like object. In both cases, the file may
@@ -311,6 +313,7 @@ class FastaQualReader(object):
 	Reader for reads that are stored in .(CS)FASTA and .QUAL files.
 	"""
 	delivers_qualities = True
+	paired = False
 
 	def __init__(self, fastafile, qualfile, sequence_class=Sequence):
 		"""
@@ -382,6 +385,8 @@ class PairedSequenceReader(object):
 	Wraps two SequenceReader instances, making sure that reads are properly
 	paired.
 	"""
+	paired = True
+
 	def __init__(self, file1, file2, colorspace=False, fileformat=None):
 		self.reader1 = open(file1, colorspace=colorspace, fileformat=fileformat)
 		self.reader2 = open(file2, colorspace=colorspace, fileformat=fileformat)
@@ -430,6 +435,8 @@ class InterleavedSequenceReader(object):
 	"""
 	Read paired-end reads from an interleaved FASTQ file.
 	"""
+	paired = True
+
 	def __init__(self, file, colorspace=False, fileformat=None):
 		self.reader = open(file, colorspace=colorspace, fileformat=fileformat)
 		self.delivers_qualities = self.reader.delivers_qualities
@@ -441,7 +448,8 @@ class InterleavedSequenceReader(object):
 			try:
 				r2 = next(it)
 			except StopIteration:
-				raise FormatError("Interleaved input file incomplete: Last record has no partner.")
+				raise FormatError("Interleaved input file incomplete: Last record "
+					"{!r} has no partner.".format(r1.name))
 			if not sequence_names_match(r1, r2):
 				raise FormatError("Reads are improperly paired. Name {0!r} "
 					"(first) does not match {1!r} (second).".format(r1.name, r2.name))
@@ -622,6 +630,7 @@ class UnknownFileType(Exception):
 	"""
 
 
+# TODO rename
 def open(file1, file2=None, qualfile=None, colorspace=False, fileformat=None,
 	interleaved=False, mode='r', qualities=None):
 	"""
@@ -689,6 +698,25 @@ def open(file1, file2=None, qualfile=None, colorspace=False, fileformat=None,
 		mode=mode, qualities=qualities)
 
 
+def _detect_format_from_name(name):
+	"""
+	name -- file name
+
+	Return 'fasta', 'fastq' or None if the format could not be detected.
+	"""
+	name = name.lower()
+	for ext in ('.gz', '.xz', '.bz2'):
+		if name.endswith(ext):
+			name = name[:-len(ext)]
+			break
+	name, ext = splitext(name)
+	if ext in ['.fasta', '.fa', '.fna', '.csfasta', '.csfa']:
+		return 'fasta'
+	elif ext in ['.fastq', '.fq'] or (ext == '.txt' and name.endswith('_sequence')):
+		return 'fastq'
+	return None
+
+
 def _seqopen1(file, colorspace=False, fileformat=None, mode='r', qualities=None):
 	"""
 	Open a single sequence file. See description above.
@@ -725,48 +753,164 @@ def _seqopen1(file, colorspace=False, fileformat=None, mode='r', qualities=None)
 	elif hasattr(file, "name"):  # seems to be an open file-like object
 		name = file.name
 
-	if name:
-		for ext in ('.gz', '.xz', '.bz2'):
-			if name.endswith(ext):
-				name = name[:-len(ext)]
-				break
-		name, ext = splitext(name)
-		ext = ext.lower()
-		if ext in ['.fasta', '.fa', '.fna', '.csfasta', '.csfa']:
+	format = _detect_format_from_name(name) if name else None
+
+	if format is None and mode == 'w' and qualities is not None:
+		# Format not recognized, but we know whether to use a format with or without qualities
+		format = 'fastq' if qualities else 'fasta'
+
+	if mode == 'r' and format is None:
+		# No format detected so far. Try to read from the file.
+		if hasattr(file, 'peek'):
+			first_char = file.peek(1)
+			new_file = file
+		else:
+			first_line = file.readline()
+			first_char = first_line[0:1]
+			new_file = FileWithPrependedLine(file, first_line)
+		if first_char == '#':
+			# A comment char - only valid for some FASTA variants (csfasta)
 			format = 'fasta'
-		elif ext in ['.fastq', '.fq'] or (ext == '.txt' and name.endswith('_sequence')):
+		elif first_char == '>':
+			format = 'fasta'
+		elif first_char == '@':
 			format = 'fastq'
-		elif mode == 'w' and qualities is True:
-			# Format not recognized, but know we want to write reads with qualities
+		elif first_char == '':
+			# Empty input. Pretend this is FASTQ
 			format = 'fastq'
-		elif mode == 'w' and qualities is False:
-			# Same, but we know that we want to write reads without qualities
-			format = 'fasta'
 		else:
-			raise UnknownFileType("Could not determine whether file {0!r} is FASTA "
-				"or FASTQ: file name extension {1!r} not recognized".format(file, ext))
-		if format == 'fastq' and qualities is False:
-			raise ValueError("Output format cannot be FASTQ since no quality "
-				"values are available.")
-		if format == 'fastq':
-			return fastq_handler(file)
-		else:
-			return fasta_handler(file)
+			raise UnknownFileType(
+				'Could not determine whether file {!r} is FASTA or FASTQ. The file extension was '
+				'not available or not recognized and the first character in the file ({!r}) is '
+				'unexpected.'.format(file, first_char))
+		file = new_file
 
-	if mode == 'w':
-		if qualities is True:
-			return fastq_handler(file)
-		elif qualities is False:
-			return fasta_handler(file)
-		raise UnknownFileType('Cannot determine whether to write in FASTA or '
-			'FASTQ format')
-	# No name available. Try to autodetect type by reading from the file.
-	for line in file:
-		if line.startswith('#'):
-			# Skip comment lines (needed for csfasta)
-			continue
-		if line.startswith('>'):
-			return fasta_handler(FileWithPrependedLine(file, line))
-		if line.startswith('@'):
-			return fastq_handler(FileWithPrependedLine(file, line))
-	raise UnknownFileType("File is neither FASTQ nor FASTA.")
+	if format is None:
+		assert mode == 'w'
+		raise UnknownFileType('Cannot determine whether to write in FASTA or FASTQ format')
+
+	if format == 'fastq' and mode == 'w' and qualities is False:
+		raise ValueError(
+			'Output format cannot be FASTQ since no quality values are available.')
+
+	return fastq_handler(file) if format == 'fastq' else fasta_handler(file)
+
+
+def find_fasta_record_end(buf, end):
+	"""
+	Search for the end of the last complete FASTA record within buf[:end]
+	"""
+	pos = buf.rfind(b'\n>', 0, end)
+	if pos != -1:
+		return pos + 1
+	if buf[0:1] == b'>':
+		return 0
+	raise FormatError('FASTA does not start with ">"')
+
+
+def find_fastq_record_end(buf, end=None):
+	"""
+	Search for the end of the last complete *two* FASTQ records in buf[:end].
+
+	Two FASTQ records are required to ensure that read pairs in interleaved
+	paired-end data are not split.
+	"""
+	linebreaks = buf.count(b'\n', 0, end)
+	right = end
+	for _ in range(linebreaks % 8 + 1):
+		right = buf.rfind(b'\n', 0, right)
+	# Note that this works even if linebreaks == 0:
+	# rfind() returns -1 and adding 1 gives index 0,
+	# which is correct.
+	return right + 1
+
+
+def read_chunks_from_file(f, buffer_size=4*1024**2):
+	"""
+	Read a chunk of complete FASTA or FASTQ records from a file.
+	The size of a chunk is at most buffer_size.
+	f needs to be a file opened in binary mode.
+
+	The yielded memoryview objects become invalid on the next iteration.
+	"""
+	# This buffer is re-used in each iteration.
+	buf = bytearray(buffer_size)
+
+	# Read one byte to determine file format.
+	# If there is a comment char, we assume FASTA!
+	start = f.readinto(memoryview(buf)[0:1])
+	if start == 1 and buf[0:1] == b'@':
+		find_record_end = find_fastq_record_end
+	elif start == 1 and buf[0:1] == b'#' or buf[0:1] == b'>':
+		find_record_end = find_fasta_record_end
+	elif start > 0:
+		raise UnknownFileType('Input file format unknown')
+
+	# Layout of buf
+	#
+	# |-- complete records --|
+	# +---+------------------+---------+-------+
+	# |   |                  |         |       |
+	# +---+------------------+---------+-------+
+	# ^   ^                   ^         ^       ^
+	# 0   start               end       bufend  len(buf)
+	#
+	# buf[0:start] is the 'leftover' data that could not be processed
+	# in the previous iteration because it contained an incomplete
+	# FASTA or FASTQ record.
+
+	while True:
+		if start == len(buf):
+			raise OverflowError('FASTA/FASTQ record does not fit into buffer')
+		bufend = f.readinto(memoryview(buf)[start:]) + start
+		if start == bufend:
+			# End of file
+			break
+		end = find_record_end(buf, bufend)
+		assert end <= bufend
+		if end > 0:
+			yield memoryview(buf)[0:end]
+		start = bufend - end
+		assert start >= 0
+		buf[0:start] = buf[end:bufend]
+
+	if start > 0:
+		yield memoryview(buf)[0:start]
+
+
+def read_paired_chunks(f, f2, buffer_size=4*1024**2):
+	buf1 = bytearray(buffer_size)
+	buf2 = bytearray(buffer_size)
+
+	# Read one byte to make sure are processing FASTQ
+	start1 = f.readinto(memoryview(buf1)[0:1])
+	start2 = f2.readinto(memoryview(buf2)[0:1])
+	if (start1 == 1 and buf1[0:1] != b'@') or (start2 == 1 and buf2[0:1] != b'@'):
+		raise FormatError('Paired-end data must be in FASTQ format when using multiple cores')
+
+	while True:
+		bufend1 = f.readinto(memoryview(buf1)[start1:]) + start1
+		if start1 == bufend1:
+			break
+		bufend2 = f2.readinto(memoryview(buf2)[start2:]) + start2
+		if start2 == bufend2:
+			break
+
+		end1, end2 = two_fastq_heads(buf1, buf2, bufend1, bufend2)
+		assert end1 <= bufend1
+		assert end2 <= bufend2
+
+		if end1 > 0 or end2 > 0:
+			yield (memoryview(buf1)[0:end1], memoryview(buf2)[0:end2])
+		start1 = bufend1 - end1
+		assert start1 >= 0
+		buf1[0:start1] = buf1[end1:bufend1]
+		start2 = bufend2 - end2
+		assert start2 >= 0
+		buf2[0:start2] = buf2[end2:bufend2]
+
+	if start1 > 0 or start2 > 0:
+		yield (memoryview(buf1)[0:start1], memoryview(buf2)[0:start2])
+
+
+from ._seqio import head, fastq_head, two_fastq_heads  # re-exported
diff --git a/tests/cut/demultiplexed.first.1.fastq b/tests/cut/demultiplexed.first.1.fastq
new file mode 100644
index 0000000..18fea89
--- /dev/null
+++ b/tests/cut/demultiplexed.first.1.fastq
@@ -0,0 +1,4 @@
+ at read3/1
+CCAACTTGATATTAAT
++
+HHHHHHHHHHHHHHHH
diff --git a/tests/cut/demultiplexed.first.2.fastq b/tests/cut/demultiplexed.first.2.fastq
new file mode 100644
index 0000000..218b013
--- /dev/null
+++ b/tests/cut/demultiplexed.first.2.fastq
@@ -0,0 +1,4 @@
+ at read3/2
+TGTTATTAATATCAAGTTGG
++
+#HHHHHHHHHHHHHHHHHHH
diff --git a/tests/cut/demultiplexed.second.1.fastq b/tests/cut/demultiplexed.second.1.fastq
new file mode 100644
index 0000000..f6281da
--- /dev/null
+++ b/tests/cut/demultiplexed.second.1.fastq
@@ -0,0 +1,4 @@
+ at read2/1
+CAACAGGCCA
++
+HHHHHHHHHH
diff --git a/tests/cut/demultiplexed.second.2.fastq b/tests/cut/demultiplexed.second.2.fastq
new file mode 100644
index 0000000..96d2253
--- /dev/null
+++ b/tests/cut/demultiplexed.second.2.fastq
@@ -0,0 +1,4 @@
+ at read2/2
+TGTGGCCTGTTG
++
+###HHHHHHHHH
diff --git a/tests/cut/demultiplexed.unknown.1.fastq b/tests/cut/demultiplexed.unknown.1.fastq
new file mode 100644
index 0000000..95c5794
--- /dev/null
+++ b/tests/cut/demultiplexed.unknown.1.fastq
@@ -0,0 +1,8 @@
+ at read1/1 some text
+TTATTTGTCTCCAGCTTAGACATATCGCCT
++
+##HHHHHHHHHHHHHHHHHHHHHHHHHHHH
+ at read4/1
+GACAGGCCGTTTGAATGTTGACGGGATGTT
++
+HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
diff --git a/tests/cut/demultiplexed.unknown.2.fastq b/tests/cut/demultiplexed.unknown.2.fastq
new file mode 100644
index 0000000..8f27823
--- /dev/null
+++ b/tests/cut/demultiplexed.unknown.2.fastq
@@ -0,0 +1,8 @@
+ at read1/2 other text
+GCTGGAGACA
++
+HHHHHHHHHH
+ at read4/2
+CATCCCGTCAACATTCAAACGGCCTGTCCA
++
+HH############################
diff --git a/tests/cut/linked-not-anchored.fasta b/tests/cut/linked-discard-g.fasta
similarity index 57%
copy from tests/cut/linked-not-anchored.fasta
copy to tests/cut/linked-discard-g.fasta
index 34074a7..f382394 100644
--- a/tests/cut/linked-not-anchored.fasta
+++ b/tests/cut/linked-discard-g.fasta
@@ -1,13 +1,7 @@
 >r1 5' adapter and 3' adapter
 CCCCCCCCCC
->r2 without any adapter
-GGGGGGGGGGGGGGGGGGG
 >r3 5' adapter, partial 3' adapter
 CCCGGCCCCC
->r4 only 3' adapter
-GGGGGGGGGGCCCCCCCCCC
->r5 only 5' adapter
-CCCCCCCCCCGGGGGGG
 >r6 partial 5' adapter
 CCCCCCCCCC
 >r7 5' adapter plus preceding bases
diff --git a/tests/cut/linked-discard.fasta b/tests/cut/linked-discard.fasta
new file mode 100644
index 0000000..831916a
--- /dev/null
+++ b/tests/cut/linked-discard.fasta
@@ -0,0 +1,6 @@
+>r1 5' adapter and 3' adapter
+CCCCCCCCCC
+>r3 5' adapter, partial 3' adapter
+CCCGGCCCCC
+>r5 only 5' adapter
+CCCCCCCCCCGGGGGGG
diff --git a/tests/cut/linked-not-anchored.fasta b/tests/cut/linked-not-anchored.fasta
index 34074a7..6e72974 100644
--- a/tests/cut/linked-not-anchored.fasta
+++ b/tests/cut/linked-not-anchored.fasta
@@ -5,9 +5,9 @@ GGGGGGGGGGGGGGGGGGG
 >r3 5' adapter, partial 3' adapter
 CCCGGCCCCC
 >r4 only 3' adapter
-GGGGGGGGGGCCCCCCCCCC
+GGGGGGGGGGCCCCCCCCCCTTTTTTTTTTGGGGGGG
 >r5 only 5' adapter
-CCCCCCCCCCGGGGGGG
+AAAAAAAAAACCCCCCCCCCGGGGGGG
 >r6 partial 5' adapter
 CCCCCCCCCC
 >r7 5' adapter plus preceding bases
diff --git a/tests/data/block1.fastq.bz2 b/tests/data/block1.fastq.bz2
deleted file mode 100644
index 8caa5bc..0000000
Binary files a/tests/data/block1.fastq.bz2 and /dev/null differ
diff --git a/tests/data/block2.fastq.bz2 b/tests/data/block2.fastq.bz2
deleted file mode 100644
index 5ed6b97..0000000
Binary files a/tests/data/block2.fastq.bz2 and /dev/null differ
diff --git a/tests/testadapters.py b/tests/test_adapters.py
similarity index 59%
rename from tests/testadapters.py
rename to tests/test_adapters.py
index f1f38e1..7081f2e 100644
--- a/tests/testadapters.py
+++ b/tests/test_adapters.py
@@ -4,7 +4,7 @@ from nose.tools import raises, assert_raises
 
 from cutadapt.seqio import Sequence
 from cutadapt.adapters import (Adapter, Match, ColorspaceAdapter, FRONT, BACK,
-	parse_braces, LinkedAdapter)
+	parse_braces, LinkedAdapter, AdapterStatistics)
 
 
 def test_issue_52():
@@ -91,7 +91,10 @@ def test_parse_braces_fail():
 
 
 def test_linked_adapter():
-	linked_adapter = LinkedAdapter('AAAA', 'TTTT')
+	linked_adapter = LinkedAdapter('AAAA', 'TTTT', min_overlap=4)
+	assert linked_adapter.front_adapter.min_overlap == 4
+	assert linked_adapter.back_adapter.min_overlap == 4
+
 	sequence = Sequence(name='seq', sequence='AAAACCCCCTTTT')
 	trimmed = linked_adapter.match_to(sequence).trimmed()
 	assert trimmed.name == 'seq'
@@ -126,16 +129,63 @@ def test_info_record():
 
 
 def test_random_match_probabilities():
-	a = Adapter('A', where=BACK, max_error_rate=0.1)
-	assert a.random_match_probabilities(0.5) == [1, 0.25]
-	assert a.random_match_probabilities(0.2) == [1, 0.4]
+	a = Adapter('A', where=BACK, max_error_rate=0.1).create_statistics()
+	assert a.back.random_match_probabilities(0.5) == [1, 0.25]
+	assert a.back.random_match_probabilities(0.2) == [1, 0.4]
 
 	for s in ('ACTG', 'XMWH'):
-		a = Adapter(s, where=BACK, max_error_rate=0.1)
-		assert a.random_match_probabilities(0.5) == [1, 0.25, 0.25**2, 0.25**3, 0.25**4]
-		assert a.random_match_probabilities(0.2) == [1, 0.4, 0.4*0.1, 0.4*0.1*0.4, 0.4*0.1*0.4*0.1]
-
-	a = Adapter('GTCA', where=FRONT, max_error_rate=0.1)
-	assert a.random_match_probabilities(0.5) == [1, 0.25, 0.25**2, 0.25**3, 0.25**4]
-	assert a.random_match_probabilities(0.2) == [1, 0.4, 0.4*0.1, 0.4*0.1*0.4, 0.4*0.1*0.4*0.1]
-
+		a = Adapter(s, where=BACK, max_error_rate=0.1).create_statistics()
+		assert a.back.random_match_probabilities(0.5) == [1, 0.25, 0.25**2, 0.25**3, 0.25**4]
+		assert a.back.random_match_probabilities(0.2) == [1, 0.4, 0.4*0.1, 0.4*0.1*0.4, 0.4*0.1*0.4*0.1]
+
+	a = Adapter('GTCA', where=FRONT, max_error_rate=0.1).create_statistics()
+	assert a.front.random_match_probabilities(0.5) == [1, 0.25, 0.25**2, 0.25**3, 0.25**4]
+	assert a.front.random_match_probabilities(0.2) == [1, 0.4, 0.4*0.1, 0.4*0.1*0.4, 0.4*0.1*0.4*0.1]
+
+
+def test_add_adapter_statistics():
+	stats = Adapter('A', name='name', where=BACK, max_error_rate=0.1).create_statistics()
+	end_stats = stats.back
+	end_stats.adjacent_bases['A'] = 7
+	end_stats.adjacent_bases['C'] = 19
+	end_stats.adjacent_bases['G'] = 23
+	end_stats.adjacent_bases['T'] = 42
+	end_stats.adjacent_bases[''] = 45
+
+	end_stats.errors[10][0] = 100
+	end_stats.errors[10][1] = 11
+	end_stats.errors[10][2] = 3
+	end_stats.errors[20][0] = 600
+	end_stats.errors[20][1] = 66
+	end_stats.errors[20][2] = 6
+
+	stats2 = Adapter('A', name='name', where=BACK, max_error_rate=0.1).create_statistics()
+	end_stats2 = stats2.back
+	end_stats2.adjacent_bases['A'] = 43
+	end_stats2.adjacent_bases['C'] = 31
+	end_stats2.adjacent_bases['G'] = 27
+	end_stats2.adjacent_bases['T'] = 8
+	end_stats2.adjacent_bases[''] = 5
+	end_stats2.errors[10][0] = 234
+	end_stats2.errors[10][1] = 14
+	end_stats2.errors[10][3] = 5
+	end_stats2.errors[15][0] = 90
+	end_stats2.errors[15][1] = 17
+	end_stats2.errors[15][2] = 2
+
+	stats += stats2
+	r = stats.back
+
+	assert r.adjacent_bases == {'A': 50, 'C': 50, 'G': 50, 'T': 50, '': 50}
+	assert r.errors == {
+		10: {0: 334, 1: 25, 2: 3, 3: 5},
+		15: {0: 90, 1: 17, 2: 2},
+		20: {0: 600, 1: 66, 2: 6},
+	}
+
+
+def test_issue_265():
+	"""Crash when accessing the matches property of non-anchored linked adapters"""
+	s = Sequence('name', 'AAAATTTT')
+	la = LinkedAdapter('GGG', 'TTT', front_anchored=False, back_anchored=False)
+	assert la.match_to(s).matches == 3
diff --git a/tests/testalign.py b/tests/test_align.py
similarity index 100%
rename from tests/testalign.py
rename to tests/test_align.py
diff --git a/tests/testcolorspace.py b/tests/test_colorspace.py
similarity index 98%
rename from tests/testcolorspace.py
rename to tests/test_colorspace.py
index 3ac3c46..b8b3ebc 100644
--- a/tests/testcolorspace.py
+++ b/tests/test_colorspace.py
@@ -2,8 +2,8 @@
 from __future__ import print_function, division, absolute_import
 
 from cutadapt.colorspace import encode, decode
-from cutadapt.scripts.cutadapt import main
-from .utils import run, datapath
+from cutadapt.__main__ import main
+from utils import run, datapath
 
 # If there are any unknown characters in the test sequence,
 # round tripping will only work if all characters after the
diff --git a/tests/tests.py b/tests/test_commandline.py
similarity index 91%
rename from tests/tests.py
rename to tests/test_commandline.py
index 6406e4c..5869987 100644
--- a/tests/tests.py
+++ b/tests/test_commandline.py
@@ -5,11 +5,16 @@
 from __future__ import print_function, division, absolute_import
 
 import os
+import shutil
+import subprocess
 import sys
+import tempfile
+
 from nose.tools import raises
-from cutadapt.scripts import cutadapt
+
+from cutadapt.__main__ import main
 from cutadapt.compat import StringIO
-from .utils import run, assert_files_equal, datapath, cutpath, redirect_stderr, temporary_path
+from utils import run, assert_files_equal, datapath, cutpath, redirect_stderr, temporary_path
 
 
 def test_example():
@@ -260,13 +265,15 @@ def test_info_file():
 	# GCCTAACTTCTTAGACTGCCTTAAGGACGT (fourth base is different)
 	#
 	with temporary_path("infotmp.txt") as infotmp:
-		run(["--info-file", infotmp, '-a', 'adapt=GCCGAACTTCTTAGACTGCCTTAAGGACGT'], "illumina.fastq", "illumina.fastq.gz")
+		run(["--info-file", infotmp, '-a', 'adapt=GCCGAACTTCTTAGACTGCCTTAAGGACGT'],
+			"illumina.fastq", "illumina.fastq.gz")
 		assert_files_equal(cutpath('illumina.info.txt'), infotmp)
 
 
 def test_info_file_times():
 	with temporary_path("infotmp.txt") as infotmp:
-		run(["--info-file", infotmp, '--times', '2', '-a', 'adapt=GCCGAACTTCTTA', '-a', 'adapt2=GACTGCCTTAAGGACGT'], "illumina5.fastq", "illumina5.fastq")
+		run(["--info-file", infotmp, '--times', '2', '-a', 'adapt=GCCGAACTTCTTA',
+			'-a', 'adapt2=GACTGCCTTAAGGACGT'], "illumina5.fastq", "illumina5.fastq")
 		assert_files_equal(cutpath('illumina5.info.txt'), infotmp)
 
 
@@ -309,19 +316,19 @@ except ImportError:
 @raises(SystemExit)
 def test_qualfile_only():
 	with redirect_stderr():
-		cutadapt.main(['file.qual'])
+		main(['file.qual'])
 
 
 @raises(SystemExit)
 def test_no_args():
 	with redirect_stderr():
-		cutadapt.main([])
+		main([])
 
 
 @raises(SystemExit)
 def test_two_fastqs():
 	with redirect_stderr():
-		cutadapt.main([datapath('paired.1.fastq'), datapath('paired.2.fastq')])
+		main([datapath('paired.1.fastq'), datapath('paired.2.fastq')])
 
 
 def test_anchored_no_indels():
@@ -342,7 +349,7 @@ def test_anchored_no_indels_wildcard_adapt():
 @raises(SystemExit)
 def test_non_iupac_characters():
 	with redirect_stderr():
-		cutadapt.main(['-a', 'ZACGT', datapath('small.fastq')])
+		main(['-a', 'ZACGT', datapath('small.fastq')])
 
 
 def test_unconditional_cut_front():
@@ -384,15 +391,14 @@ def test_adapter_file_3p_anchored_no_indels():
 
 
 def test_demultiplex():
-	multiout = os.path.join(os.path.dirname(__file__), 'data', 'tmp-demulti.{name}.fasta')
+	tempdir = tempfile.mkdtemp(prefix='cutadapt-tests.')
+	multiout = os.path.join(tempdir, 'tmp-demulti.{name}.fasta')
 	params = ['-a', 'first=AATTTCAGGAATT', '-a', 'second=GTTCTCTAGTTCT', '-o', multiout, datapath('twoadapters.fasta')]
-	assert cutadapt.main(params) is None
+	assert main(params) is None
 	assert_files_equal(cutpath('twoadapters.first.fasta'), multiout.format(name='first'))
 	assert_files_equal(cutpath('twoadapters.second.fasta'), multiout.format(name='second'))
 	assert_files_equal(cutpath('twoadapters.unknown.fasta'), multiout.format(name='unknown'))
-	os.remove(multiout.format(name='first'))
-	os.remove(multiout.format(name='second'))
-	os.remove(multiout.format(name='unknown'))
+	shutil.rmtree(tempdir)
 
 
 def test_max_n():
@@ -411,7 +417,7 @@ def test_quiet_is_quiet():
 	try:
 		sys.stdout = captured_standard_output
 		sys.stderr = captured_standard_error
-		cutadapt.main(['-o', '/dev/null', '--quiet', '-a', 'XXXX', datapath('illumina.fastq.gz')])
+		main(['-o', '/dev/null', '--quiet', '-a', 'XXXX', datapath('illumina.fastq.gz')])
 	finally:
 		sys.stdout = old_stdout
 		sys.stderr = old_stderr
@@ -443,22 +449,30 @@ def test_linked_5p_not_anchored():
 	run('-g AAAAAAAAAA...TTTTTTTTTT', 'linked-not-anchored.fasta', 'linked.fasta')
 
 
+def test_linked_discard_untrimmed():
+	run('-a AAAAAAAAAA...TTTTTTTTTT --discard-untrimmed', 'linked-discard.fasta', 'linked.fasta')
+
+
+def test_linked_discard_untrimmed_g():
+	run('-g AAAAAAAAAA...TTTTTTTTTT --discard-untrimmed', 'linked-discard-g.fasta', 'linked.fasta')
+
+
 @raises(SystemExit)
 def test_linked_anywhere():
 	with redirect_stderr():
-		cutadapt.main(['-b', 'AAA...TTT', datapath('linked.fasta')])
+		main(['-b', 'AAA...TTT', datapath('linked.fasta')])
 
 
 @raises(SystemExit)
 def test_anywhere_anchored_5p():
 	with redirect_stderr():
-		cutadapt.main(['-b', '^AAA', datapath('small.fastq')])
+		main(['-b', '^AAA', datapath('small.fastq')])
 
 
 @raises(SystemExit)
 def test_anywhere_anchored_3p():
 	with redirect_stderr():
-		cutadapt.main(['-b', 'TTT$', datapath('small.fastq')])
+		main(['-b', 'TTT$', datapath('small.fastq')])
 
 
 def test_fasta():
@@ -476,3 +490,7 @@ def test_issue_202():
 
 def test_length():
 	run('--length 5', 'shortened.fastq', 'small.fastq')
+
+
+def test_run_cutadapt_process():
+	subprocess.check_call(['cutadapt', '--version'])
diff --git a/tests/testfilters.py b/tests/test_filters.py
similarity index 80%
rename from tests/testfilters.py
rename to tests/test_filters.py
index 3976e72..805a5fa 100644
--- a/tests/testfilters.py
+++ b/tests/test_filters.py
@@ -4,9 +4,10 @@ Tests write output (should it return True or False or write)
 """
 from __future__ import print_function, division, absolute_import
 
-from cutadapt.filters import NContentFilter, DISCARD, KEEP, LegacyPairedRedirector, PairedRedirector
+from cutadapt.filters import NContentFilter, DISCARD, KEEP, PairedRedirector
 from cutadapt.seqio import Sequence
 
+
 def test_ncontentfilter():
 	# third parameter is True if read should be discarded
 	params = [
@@ -33,10 +34,10 @@ def test_ncontentfilter_paired():
 	]
 	for seq1, seq2, count, expected in params:
 		filter = NContentFilter(count=count)
-		filter_legacy = LegacyPairedRedirector(None, filter)
-		filter_both = PairedRedirector(None, filter)
+		filter_legacy = PairedRedirector(None, filter, pair_filter_mode='first')
+		filter_any = PairedRedirector(None, filter, pair_filter_mode='any')
 		read1 = Sequence('read1', seq1, qualities='#'*len(seq1))
 		read2 = Sequence('read1', seq2, qualities='#'*len(seq2))
 		assert filter_legacy(read1, read2) == filter(read1)
 		# discard entire pair if one of the reads fulfills criteria
-		assert filter_both(read1, read2) == expected
+		assert filter_any(read1, read2) == expected
diff --git a/tests/testmodifiers.py b/tests/test_modifiers.py
similarity index 100%
rename from tests/testmodifiers.py
rename to tests/test_modifiers.py
diff --git a/tests/testpaired.py b/tests/test_paired.py
similarity index 56%
rename from tests/testpaired.py
rename to tests/test_paired.py
index 5209af3..667f0a7 100644
--- a/tests/testpaired.py
+++ b/tests/test_paired.py
@@ -1,25 +1,42 @@
 # coding: utf-8
 from __future__ import print_function, division, absolute_import
 
+import os.path
 import shutil
+import tempfile
+
+import pytest
 from nose.tools import raises
-from cutadapt.scripts import cutadapt
-from .utils import run, assert_files_equal, datapath, cutpath, redirect_stderr, temporary_path
 
+from cutadapt.compat import PY3
+from cutadapt.__main__ import main
+from utils import run, assert_files_equal, datapath, cutpath, redirect_stderr, temporary_path
+
+
+if PY3:
+	@pytest.fixture(params=[1, 2])
+	def cores(request):
+		return request.param
+else:
+	@pytest.fixture
+	def cores():
+		return 1
 
-def run_paired(params, in1, in2, expected1, expected2):
+
+def run_paired(params, in1, in2, expected1, expected2, cores):
 	if type(params) is str:
 		params = params.split()
+	params += ['--cores', str(cores), '--buffer-size=512']
 	with temporary_path('tmp1-' + expected1) as p1:
 		with temporary_path('tmp2-' + expected2) as p2:
 			params += ['-o', p1, '-p', p2]
 			params += [datapath(in1), datapath(in2)]
-			assert cutadapt.main(params) is None
+			assert main(params) is None
 			assert_files_equal(cutpath(expected1), p1)
 			assert_files_equal(cutpath(expected2), p2)
 
 
-def run_interleaved(params, inpath1, inpath2=None, expected1=None, expected2=None):
+def run_interleaved(params, inpath1, inpath2=None, expected1=None, expected2=None, cores=1):
 	"""
 	Interleaved input or output (or both)
 	"""
@@ -28,7 +45,7 @@ def run_interleaved(params, inpath1, inpath2=None, expected1=None, expected2=Non
 	assert not (inpath2 and not inpath1)
 	if type(params) is str:
 		params = params.split()
-	params += ['--interleaved']
+	params += ['--interleaved', '--cores', str(cores), '--buffer-size=512']
 	with temporary_path('tmp1-' + expected1) as tmp1:
 		params += ['-o', tmp1]
 		paths = [datapath(inpath1)]
@@ -37,10 +54,10 @@ def run_interleaved(params, inpath1, inpath2=None, expected1=None, expected2=Non
 		if expected2:
 			with temporary_path('tmp2-' + expected2) as tmp2:
 				params += ['-p', tmp2]
-				assert cutadapt.main(params + paths) is None
+				assert main(params + paths) is None
 				assert_files_equal(cutpath(expected2), tmp2)
 		else:
-			assert cutadapt.main(params + paths) is None
+			assert main(params + paths) is None
 		assert_files_equal(cutpath(expected1), tmp1)
 
 
@@ -50,7 +67,7 @@ def test_paired_separate():
 	run('-a CAGTGGAGTA', 'paired-separate.2.fastq', 'paired.2.fastq')
 
 
-def test_paired_end_legacy():
+def test_paired_end_legacy(cores):
 	"""--paired-output, not using -A/-B/-G"""
 	# the -m 14 filters out one read, which should then also be filtered out in the second output file
 	# -q 10 should not change anything: qualities in file 1 are high enough,
@@ -58,7 +75,8 @@ def test_paired_end_legacy():
 	run_paired(
 		'-a TTAGACATAT -m 14 -q 10',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='paired.m14.1.fastq', expected2='paired.m14.2.fastq'
+		expected1='paired.m14.1.fastq', expected2='paired.m14.2.fastq',
+		cores=cores
 	)
 
 
@@ -70,7 +88,8 @@ def test_untrimmed_paired_output():
 					'--untrimmed-output', untrimmed1,
 					'--untrimmed-paired-output', untrimmed2],
 				in1='paired.1.fastq', in2='paired.2.fastq',
-				expected1='paired-trimmed.1.fastq', expected2='paired-trimmed.2.fastq'
+				expected1='paired-trimmed.1.fastq', expected2='paired-trimmed.2.fastq',
+				cores=1
 			)
 			assert_files_equal(cutpath('paired-untrimmed.1.fastq'), untrimmed1)
 			assert_files_equal(cutpath('paired-untrimmed.2.fastq'), untrimmed2)
@@ -86,20 +105,21 @@ def test_explicit_format_with_paired():
 				'--format=fastq -a TTAGACATAT -m 14',
 				in1=txt1, in2=txt2,
 				expected1='paired.m14.1.fastq',
-				expected2='paired.m14.2.fastq'
+				expected2='paired.m14.2.fastq',
+				cores=1
 			)
 
 
 def test_no_trimming_legacy():
 	# make sure that this doesn't divide by zero
-	cutadapt.main([
+	main([
 		'-a', 'XXXXX', '-o', '/dev/null', '-p', '/dev/null',
 		datapath('paired.1.fastq'), datapath('paired.2.fastq')])
 
 
 def test_no_trimming():
 	# make sure that this doesn't divide by zero
-	cutadapt.main([
+	main([
 		'-a', 'XXXXX', '-A', 'XXXXX', '-o', '/dev/null', '-p', '/dev/null',
 		datapath('paired.1.fastq'), datapath('paired.2.fastq')])
 
@@ -107,81 +127,89 @@ def test_no_trimming():
 @raises(SystemExit)
 def test_missing_file():
 	with redirect_stderr():
-		cutadapt.main(['-a', 'XX', '--paired-output', 'out.fastq', datapath('paired.1.fastq')])
-
-
- at raises(SystemExit)
-def test_first_too_short():
-	with temporary_path("truncated.1.fastq") as trunc1:
-		# Create a truncated file in which the last read is missing
-		with open(datapath('paired.1.fastq')) as f:
-			lines = f.readlines()
-			lines = lines[:-4]
-		with open(trunc1, 'w') as f:
-			f.writelines(lines)
-		with redirect_stderr():
-			cutadapt.main(
-				'-a XX -o /dev/null --paired-output out.fastq'.split() +
-				[trunc1, datapath('paired.2.fastq')]
-			)
-
-
- at raises(SystemExit)
-def test_second_too_short():
-	with temporary_path("truncated.2.fastq") as trunc2:
-		# Create a truncated file in which the last read is missing
-		with open(datapath('paired.2.fastq')) as f:
-			lines = f.readlines()
-			lines = lines[:-4]
-		with open(trunc2, 'w') as f:
-			f.writelines(lines)
-		with redirect_stderr():
-			cutadapt.main('-a XX -o /dev/null --paired-output out.fastq'.split() +
-				[datapath('paired.1.fastq'), trunc2])
-
-
- at raises(SystemExit)
-def test_unmatched_read_names():
-	with temporary_path("swapped.1.fastq") as swapped:
-		# Create a file in which reads 2 and are swapped
-		with open(datapath('paired.1.fastq')) as f:
-			lines = f.readlines()
-			lines = lines[0:4] + lines[8:12] + lines[4:8] + lines[12:]
-		with open(swapped, 'w') as f:
-			f.writelines(lines)
-		with redirect_stderr():
-			cutadapt.main('-a XX -o out1.fastq --paired-output out2.fastq'.split() +
-				[swapped, datapath('paired.2.fastq')])
+		main(['-a', 'XX', '--paired-output', 'out.fastq', datapath('paired.1.fastq')])
+
+
+def test_first_too_short(cores):
+	with pytest.raises(SystemExit):
+		with temporary_path("truncated.1.fastq") as trunc1:
+			# Create a truncated file in which the last read is missing
+			with open(datapath('paired.1.fastq')) as f:
+				lines = f.readlines()
+				lines = lines[:-4]
+			with open(trunc1, 'w') as f:
+				f.writelines(lines)
+			with redirect_stderr():
+				main(
+					'-a XX -o /dev/null --paired-output out.fastq'.split()
+					+ ['--cores', str(cores)]
+					+ [trunc1, datapath('paired.2.fastq')]
+				)
+
+
+def test_second_too_short(cores):
+	with pytest.raises(SystemExit):
+		with temporary_path("truncated.2.fastq") as trunc2:
+			# Create a truncated file in which the last read is missing
+			with open(datapath('paired.2.fastq')) as f:
+				lines = f.readlines()
+				lines = lines[:-4]
+			with open(trunc2, 'w') as f:
+				f.writelines(lines)
+			with redirect_stderr():
+				main('-a XX -o /dev/null --paired-output out.fastq'.split()
+					+ ['--cores', str(cores)]
+					+ [datapath('paired.1.fastq'), trunc2])
+
+
+def test_unmatched_read_names(cores):
+	with pytest.raises(SystemExit):
+		with temporary_path("swapped.1.fastq") as swapped:
+			# Create a file in which reads 2 and 1 are swapped
+			with open(datapath('paired.1.fastq')) as f:
+				lines = f.readlines()
+				lines = lines[0:4] + lines[8:12] + lines[4:8] + lines[12:]
+			with open(swapped, 'w') as f:
+				f.writelines(lines)
+			with redirect_stderr():
+				main('-a XX -o out1.fastq --paired-output out2.fastq'.split()
+					+ ['--cores', str(cores)]
+					+ [swapped, datapath('paired.2.fastq')])
 
 
- at raises(SystemExit)
-def test_p_without_o():
+def test_p_without_o(cores):
 	"""Option -p given but -o missing"""
-	cutadapt.main('-a XX -p /dev/null'.split() +
-		[datapath('paired.1.fastq'), datapath('paired.2.fastq')])
+	with pytest.raises(SystemExit):
+		main('-a XX -p /dev/null'.split()
+			+ ['--cores', str(cores)]
+			+ [datapath('paired.1.fastq'), datapath('paired.2.fastq')])
 
 
- at raises(SystemExit)
-def test_paired_but_only_one_input_file():
+def test_paired_but_only_one_input_file(cores):
 	"""Option -p given but only one input file"""
-	cutadapt.main('-a XX -o /dev/null -p /dev/null'.split() + [datapath('paired.1.fastq')])
+	with pytest.raises(SystemExit):
+		main('-a XX -o /dev/null -p /dev/null'.split()
+			+ ['--cores', str(cores)]
+			+ [datapath('paired.1.fastq')])
 
 
-def test_legacy_minlength():
+def test_legacy_minlength(cores):
 	"""Ensure -m is not applied to second read in a pair in legacy mode"""
 	run_paired(
 		'-a XXX -m 27',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='paired-m27.1.fastq', expected2='paired-m27.2.fastq'
+		expected1='paired-m27.1.fastq', expected2='paired-m27.2.fastq',
+		cores=cores
 	)
 
 
-def test_paired_end():
+def test_paired_end(cores):
 	"""single-pass paired-end with -m"""
 	run_paired(
 		'-a TTAGACATAT -A CAGTGGAGTA -m 14',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='paired.1.fastq', expected2='paired.2.fastq'
+		expected1='paired.1.fastq', expected2='paired.2.fastq',
+		cores=cores
 	)
 
 
@@ -189,85 +217,95 @@ def test_paired_anchored_back_no_indels():
 	run_paired(
 		'-a BACKADAPTER$ -A BACKADAPTER$ -N --no-indels',
 		in1='anchored-back.fasta', in2='anchored-back.fasta',
-		expected1='anchored-back.fasta', expected2="anchored-back.fasta"
+		expected1='anchored-back.fasta', expected2="anchored-back.fasta",
+		cores=1
 	)
 
 
-def test_paired_end_qualtrim():
+def test_paired_end_qualtrim(cores):
 	"""single-pass paired-end with -q and -m"""
 	run_paired(
 		'-q 20 -a TTAGACATAT -A CAGTGGAGTA -m 14 -M 90',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='pairedq.1.fastq', expected2='pairedq.2.fastq'
+		expected1='pairedq.1.fastq', expected2='pairedq.2.fastq',
+		cores=cores
 	)
 
 
-def test_paired_end_qualtrim_swapped():
+def test_paired_end_qualtrim_swapped(cores):
 	"""single-pass paired-end with -q and -m, but files swapped"""
 	run_paired(
 		'-q 20 -a CAGTGGAGTA -A TTAGACATAT -m 14',
 		in1='paired.2.fastq', in2='paired.1.fastq',
-		expected1='pairedq.2.fastq', expected2='pairedq.1.fastq'
+		expected1='pairedq.2.fastq', expected2='pairedq.1.fastq',
+		cores=cores
 	)
 
 
-def test_paired_end_cut():
+def test_paired_end_cut(cores):
 	run_paired(
 		'-u 3 -u -1 -U 4 -U -2',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='pairedu.1.fastq', expected2='pairedu.2.fastq'
+		expected1='pairedu.1.fastq', expected2='pairedu.2.fastq',
+		cores=cores
 	)
 
 
-def test_paired_end_upper_a_only():
+def test_paired_end_upper_a_only(cores):
 	run_paired(
 		'-A CAGTGGAGTA',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='paired-onlyA.1.fastq', expected2='paired-onlyA.2.fastq'
+		expected1='paired-onlyA.1.fastq', expected2='paired-onlyA.2.fastq',
+		cores=cores
 	)
 
 
-def test_discard_untrimmed():
+def test_discard_untrimmed(cores):
 	# issue #146
 	# the first adapter is a sequence cut out from the first read
 	run_paired(
 		'-a CTCCAGCTTAGACATATC -A XXXXXXXX --discard-untrimmed',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='empty.fastq', expected2='empty.fastq'
+		expected1='empty.fastq', expected2='empty.fastq',
+		cores=cores
 	)
 
 
-def test_discard_trimmed():
+def test_discard_trimmed(cores):
 	run_paired(
 		'-A C -O 1 --discard-trimmed',  # applies everywhere
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='empty.fastq', expected2='empty.fastq'
+		expected1='empty.fastq', expected2='empty.fastq',
+		cores=cores
 	)
 
 
-def test_interleaved_in_and_out():
+def test_interleaved_in_and_out(cores):
 	"""Single-pass interleaved paired-end with -q and -m"""
 	run_interleaved(
 		'-q 20 -a TTAGACATAT -A CAGTGGAGTA -m 14 -M 90',
-		inpath1='interleaved.fastq', expected1='interleaved.fastq'
+		inpath1='interleaved.fastq', expected1='interleaved.fastq',
+		cores=cores
 	)
 
 
-def test_interleaved_in():
+def test_interleaved_in(cores):
 	"""Interleaved input, two files output"""
 	run_interleaved(
 		'-q 20 -a TTAGACATAT -A CAGTGGAGTA -m 14 -M 90',
 		inpath1='interleaved.fastq',
-		expected1='pairedq.1.fastq', expected2='pairedq.2.fastq'
+		expected1='pairedq.1.fastq', expected2='pairedq.2.fastq',
+		cores=cores
 	)
 
 
-def test_interleaved_out():
+def test_interleaved_out(cores):
 	"""Two files input, interleaved output"""
 	run_interleaved(
 		'-q 20 -a TTAGACATAT -A CAGTGGAGTA -m 14 -M 90',
 		inpath1='paired.1.fastq', inpath2='paired.2.fastq',
-		expected1='interleaved.fastq'
+		expected1='interleaved.fastq',
+		cores=cores
 	)
 
 
@@ -279,14 +317,15 @@ def test_interleaved_neither_nor():
 			params = '-a XX --interleaved'.split()
 			with redirect_stderr():
 				params += ['-o', p1, '-p1', p2, 'paired.1.fastq', 'paired.2.fastq']
-				cutadapt.main(params)
+				main(params)
 
 
-def test_pair_filter():
+def test_pair_filter(cores):
 	run_paired(
 		'--pair-filter=both -a TTAGACATAT -A GGAGTA -m 14',
 		in1='paired.1.fastq', in2='paired.2.fastq',
-		expected1='paired-filterboth.1.fastq', expected2='paired-filterboth.2.fastq'
+		expected1='paired-filterboth.1.fastq', expected2='paired-filterboth.2.fastq',
+		cores=cores
 	)
 
 
@@ -297,7 +336,8 @@ def test_too_short_paired_output():
 				'-a TTAGACATAT -A CAGTGGAGTA -m 14 --too-short-output '
 				'{0} --too-short-paired-output {1}'.format(p1, p2),
 				in1='paired.1.fastq', in2='paired.2.fastq',
-				expected1='paired.1.fastq', expected2='paired.2.fastq'
+				expected1='paired.1.fastq', expected2='paired.2.fastq',
+				cores=1
 			)
 			assert_files_equal(cutpath('paired-too-short.1.fastq'), p1)
 			assert_files_equal(cutpath('paired-too-short.2.fastq'), p2)
@@ -310,7 +350,8 @@ def test_too_long_output():
 				'-a TTAGACATAT -A CAGTGGAGTA -M 14 --too-long-output '
 				'{0} --too-long-paired-output {1}'.format(p1, p2),
 				in1='paired.1.fastq', in2='paired.2.fastq',
-				expected1='paired-too-short.1.fastq', expected2='paired-too-short.2.fastq'
+				expected1='paired-too-short.1.fastq', expected2='paired-too-short.2.fastq',
+				cores=1
 			)
 			assert_files_equal(cutpath('paired.1.fastq'), p1)
 			assert_files_equal(cutpath('paired.2.fastq'), p2)
@@ -323,5 +364,31 @@ def test_too_short_output_paired_option_missing():
 			'-a TTAGACATAT -A CAGTGGAGTA -m 14 --too-short-output '
 			'{0}'.format(p1),
 			in1='paired.1.fastq', in2='paired.2.fastq',
-			expected1='paired.1.fastq', expected2='paired.2.fastq'
+			expected1='paired.1.fastq', expected2='paired.2.fastq',
+			cores=1
 		)
+
+
+def test_nextseq_paired(cores):
+	run_paired('--nextseq-trim 22', in1='nextseq.fastq', in2='nextseq.fastq',
+		expected1='nextseq.fastq', expected2='nextseq.fastq',
+		cores=cores)
+
+
+def test_paired_demultiplex():
+	tempdir = tempfile.mkdtemp(prefix='cutadapt-tests.')
+	multiout1 = os.path.join(tempdir, 'demultiplexed.{name}.1.fastq')
+	multiout2 = os.path.join(tempdir, 'demultiplexed.{name}.2.fastq')
+	params = [
+		'-a', 'first=AACATTAGACA', '-a', 'second=CATTAGACATATCGG',
+		'-A', 'ignored=CAGTGGAGTA', '-A', 'alsoignored=AATAACAGTGGAGTA',
+		'-o', multiout1, '-p', multiout2,
+		datapath('paired.1.fastq'), datapath('paired.2.fastq')]
+	assert main(params) is None
+	assert_files_equal(cutpath('demultiplexed.first.1.fastq'), multiout1.format(name='first'))
+	assert_files_equal(cutpath('demultiplexed.second.1.fastq'), multiout1.format(name='second'))
+	assert_files_equal(cutpath('demultiplexed.unknown.1.fastq'), multiout1.format(name='unknown'))
+	assert_files_equal(cutpath('demultiplexed.first.2.fastq'), multiout2.format(name='first'))
+	assert_files_equal(cutpath('demultiplexed.second.2.fastq'), multiout2.format(name='second'))
+	assert_files_equal(cutpath('demultiplexed.unknown.2.fastq'), multiout2.format(name='unknown'))
+	shutil.rmtree(tempdir)
diff --git a/tests/testqualtrim.py b/tests/test_qualtrim.py
similarity index 99%
rename from tests/testqualtrim.py
rename to tests/test_qualtrim.py
index 173b264..d17184a 100644
--- a/tests/testqualtrim.py
+++ b/tests/test_qualtrim.py
@@ -4,6 +4,7 @@ from __future__ import print_function, division, absolute_import
 from cutadapt.seqio import Sequence
 from cutadapt.qualtrim import nextseq_trim_index
 
+
 def test_nextseq_trim():
 	s = Sequence('n', '', '')
 	assert nextseq_trim_index(s, cutoff=22) == 0
diff --git a/tests/testseqio.py b/tests/test_seqio.py
similarity index 75%
rename from tests/testseqio.py
rename to tests/test_seqio.py
index 063d08e..0fec6e4 100644
--- a/tests/testseqio.py
+++ b/tests/test_seqio.py
@@ -1,16 +1,18 @@
 # coding: utf-8
 from __future__ import print_function, division, absolute_import
 
-import sys
+from io import BytesIO
 import os
 import shutil
 from textwrap import dedent
+import pytest
 from nose.tools import raises
 from tempfile import mkdtemp
 from cutadapt.seqio import (Sequence, ColorspaceSequence, FormatError,
 	FastaReader, FastqReader, FastaQualReader, InterleavedSequenceReader,
 	FastaWriter, FastqWriter, InterleavedSequenceWriter, open as openseq,
-	sequence_names_match)
+	sequence_names_match, head, fastq_head, two_fastq_heads, find_fastq_record_end,
+	read_paired_chunks, read_chunks_from_file)
 from cutadapt.compat import StringIO
 
 
@@ -20,7 +22,7 @@ simple_fastq = [
 	Sequence("second_sequence", "SEQUENCE2", "83<??:(61")
 	]
 
-simple_fasta = [ Sequence(x.name, x.sequence, None) for x in simple_fastq ]
+simple_fasta = [Sequence(x.name, x.sequence, None) for x in simple_fastq]
 
 
 class TestSequence:
@@ -350,3 +352,76 @@ class TestPairedSequenceReader:
 		assert match('abc1', 'abc2')
 		assert not match('abc', 'xyz')
 
+
+def test_head():
+	# no line break at end on purpose
+	buf = b'first\nsecond\nthird\nfourth\nfifth'
+	assert head(buf, 0) == 0
+	assert head(buf, 1) == len('first\n')
+	assert head(buf, 2) == len('first\n') + len('second\n')
+	assert head(buf, 3) == len('first\n') + len('second\n') + len('third\n')
+	assert head(buf, 4) == len('first\n') + len('second\n') + len('third\n') + len('fourth\n')
+	assert head(buf, 5) == len(buf)
+	assert head(buf, 6) == len(buf)
+	assert head(buf, 100) == len(buf)
+
+
+def test_two_fastq_heads():
+	buf1 = b'first\nsecond\nthird\nfourth\nfifth'
+	buf2 = b'a\nb\nc\nd\ne\nf\ng'
+	assert two_fastq_heads(buf1, buf2, len(buf1), len(buf2)) == (
+		len(b'first\nsecond\nthird\nfourth\n'), len(b'a\nb\nc\nd\n'))
+
+	assert two_fastq_heads(b'abc', b'def', 3, 3) == (0, 0)
+	assert two_fastq_heads(b'abc\n', b'def', 4, 3) == (0, 0)
+	assert two_fastq_heads(b'abc', b'def\n', 3, 4) == (0, 0)
+	assert two_fastq_heads(b'\n\n\n\n', b'\n\n\n\n', 4, 4) == (4, 4)
+
+
+def test_fastq_head():
+	assert fastq_head(b'') == 0
+	assert fastq_head(b'A\n') == 0
+	assert fastq_head(b'A\nB') == 0
+	assert fastq_head(b'A\nB\n') == 0
+	assert fastq_head(b'A\nB\nC') == 0
+	assert fastq_head(b'A\nB\nC\n') == 0
+	assert fastq_head(b'A\nB\nC\nD') == 0
+	assert fastq_head(b'A\nB\nC\nD\n') == 8
+	assert fastq_head(b'A\nB\nC\nD\nE') == 8
+
+
+def test_fastq_record_end():
+	assert find_fastq_record_end(b'') == 0
+	assert find_fastq_record_end(b'A\n') == 0
+	assert find_fastq_record_end(b'A\nB') == 0
+	assert find_fastq_record_end(b'A\nB\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC') == 0
+	assert find_fastq_record_end(b'A\nB\nC\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG\n') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG\nH') == 0
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG\nH\n') == 16
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG\nH\nI') == 16
+	assert find_fastq_record_end(b'A\nB\nC\nD\nE\nF\nG\nH\nI\n') == 16
+
+
+def test_read_paired_chunks():
+	with open('tests/data/paired.1.fastq', 'rb') as f1:
+		with open('tests/data/paired.2.fastq', 'rb') as f2:
+			for c1, c2 in read_paired_chunks(f1, f2, buffer_size=128):
+				print(c1, c2)
+
+
+def test_read_chunks_from_file():
+	for data in [b'@r1\nACG\n+\nHHH\n', b'>r1\nACGACGACG\n']:
+		assert [m.tobytes() for m in read_chunks_from_file(BytesIO(data))] == [data]
+
+		# Buffer too small
+		with pytest.raises(OverflowError):
+			list(read_chunks_from_file(BytesIO(data), buffer_size=4))
diff --git a/tests/test_trim.py b/tests/test_trim.py
new file mode 100644
index 0000000..3756f99
--- /dev/null
+++ b/tests/test_trim.py
@@ -0,0 +1,57 @@
+# coding: utf-8
+from __future__ import print_function, division, absolute_import
+
+from cutadapt.seqio import ColorspaceSequence, Sequence
+from cutadapt.adapters import Adapter, ColorspaceAdapter, PREFIX, BACK
+from cutadapt.modifiers import AdapterCutter
+
+
+def test_cs_5p():
+	read = ColorspaceSequence("name", "0123", "DEFG", "T")
+	adapter = ColorspaceAdapter("CG", PREFIX, 0.1)
+	cutter = AdapterCutter([adapter])
+	trimmed_read = cutter(read)
+	# no assertion here, just make sure the above code runs without
+	# an exception
+
+
+def test_statistics():
+	read = Sequence('name', 'AAAACCCCAAAA')
+	adapters = [Adapter('CCCC', BACK, 0.1)]
+	cutter = AdapterCutter(adapters, times=3)
+	trimmed_read = cutter(read)
+	# TODO make this a lot simpler
+	trimmed_bp = 0
+	for adapter in adapters:
+		for d in (cutter.adapter_statistics[adapter].front.lengths,
+				cutter.adapter_statistics[adapter].back.lengths):
+			trimmed_bp += sum(seqlen * count for (seqlen, count) in d.items())
+	assert trimmed_bp <= len(read), trimmed_bp
+
+
+def test_end_trim_with_mismatch():
+	"""
+	Test the not-so-obvious case where an adapter of length 13 is trimmed from
+	the end of a sequence with overlap 9 and there is one deletion.
+	In this case the algorithm starts with 10 bases of the adapter to get
+	the hit and so the match is considered good. An insertion or substitution
+	at the same spot is not a match.
+	"""
+	adapter = Adapter('TCGATCGATCGAT', BACK, 0.1)
+
+	read = Sequence('foo1', 'AAAAAAAAAAATCGTCGATC')
+	cutter = AdapterCutter([adapter], times=1)
+	trimmed_read = cutter(read)
+
+	assert trimmed_read.sequence == 'AAAAAAAAAAA'
+	assert cutter.adapter_statistics[adapter].back.lengths == {9: 1}
+	# We see 1 error at length 9 even though the number of allowed mismatches at
+	# length 9 is 0.
+	assert cutter.adapter_statistics[adapter].back.errors[9][1] == 1
+
+	read = Sequence('foo2', 'AAAAAAAAAAATCGAACGA')
+	cutter = AdapterCutter([adapter], times=1)
+	trimmed_read = cutter(read)
+
+	assert trimmed_read.sequence == read.sequence
+	assert cutter.adapter_statistics[adapter].back.lengths == {}
diff --git a/tests/testtrim.py b/tests/testtrim.py
deleted file mode 100644
index 6e94004..0000000
--- a/tests/testtrim.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# coding: utf-8
-from __future__ import print_function, division, absolute_import
-
-from cutadapt.seqio import ColorspaceSequence, Sequence
-from cutadapt.adapters import Adapter, ColorspaceAdapter, PREFIX, BACK
-from cutadapt.modifiers import AdapterCutter
-
-
-def test_cs_5p():
-	read = ColorspaceSequence("name", "0123", "DEFG", "T")
-	adapter = ColorspaceAdapter("CG", PREFIX, 0.1)
-	cutter = AdapterCutter([adapter])
-	trimmed_read = cutter(read)
-	# no assertion here, just make sure the above code runs without
-	# an exception
-
-
-def test_statistics():
-	read = Sequence('name', 'AAAACCCCAAAA')
-	adapters = [Adapter('CCCC', BACK, 0.1)]
-	cutter = AdapterCutter(adapters, times=3)
-	trimmed_read = cutter(read)
-	# TODO make this a lot simpler
-	trimmed_bp = 0
-	for adapter in adapters:
-		for d in (cutter.adapter_statistics[adapter].lengths_front, cutter.adapter_statistics[adapter].lengths_back):
-			trimmed_bp += sum(seqlen * count for (seqlen, count) in d.items())
-	assert trimmed_bp <= len(read), trimmed_bp
diff --git a/tests/utils.py b/tests/utils.py
index 1d82f46..ff01ccf 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,10 +1,14 @@
 # coding: utf-8
 from __future__ import print_function, division, absolute_import
 
-import sys, os
+import os.path
 import subprocess
+import sys
 from contextlib import contextmanager
-from cutadapt.scripts import cutadapt
+from shutil import rmtree
+from tempfile import mkdtemp
+
+from cutadapt.__main__ import main
 
 
 @contextmanager
@@ -18,12 +22,12 @@ def redirect_stderr():
 
 @contextmanager
 def temporary_path(name):
-	directory = os.path.join(os.path.dirname(__file__), 'testtmp')
-	if not os.path.isdir(directory):
-		os.mkdir(directory)
-	path = os.path.join(directory, name)
-	yield path
-	os.remove(path)
+	tempdir = mkdtemp(prefix='cutadapt-tests.')
+	path = os.path.join(tempdir, name)
+	try:
+		yield path
+	finally:
+		rmtree(tempdir)
 
 
 def datapath(path):
@@ -51,11 +55,11 @@ def run(params, expected, inpath, inpath2=None):
 	if type(params) is str:
 		params = params.split()
 	with temporary_path(expected) as tmp_fastaq:
-		params += ['-o', tmp_fastaq ] # TODO not parallelizable
-		params += [ datapath(inpath) ]
+		params += ['-o', tmp_fastaq]  # TODO not parallelizable
+		params += [datapath(inpath)]
 		if inpath2:
-			params += [ datapath(inpath2) ]
-		assert cutadapt.main(params) is None
+			params += [datapath(inpath2)]
+		assert main(params) is None
 		# TODO redirect standard output
 		assert_files_equal(cutpath(expected), tmp_fastaq)
 	# TODO diff log files

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-cutadapt.git



More information about the debian-med-commit mailing list