diff --git a/README.md b/README.md
deleted file mode 100644
index dc1f7ee..0000000
--- a/README.md
+++ /dev/null
@@ -1,147 +0,0 @@
-PySoundFile
-===========
-
-PySoundFile is an audio library based on libsndfile, CFFI and Numpy
-
-PySoundFile can read and write sound files. File reading/writing is
-supported through [libsndfile][], which is a free, cross-platform,
-open-source library for reading and writing many different sampled
-sound file formats that runs on many platforms including Windows, OS
-X, and Unix. It is accessed through [CFFI][], which is a foreight
-function interface for Python calling C code. CFFI is supported for
-CPython 2.6+, 3.x and PyPy 2.0+. PySoundFile represents audio data as
-NumPy arrays.
-
-[libsndfile]: http://www.mega-nerd.com/libsndfile/
-[CFFI]: http://cffi.readthedocs.org/
-
-PySoundFile is BSD licensed.
-(c) 2013, Bastian Bechtold
-
-Installation
-------------
-
-On the Python side, you need to have CFFI and Numpy in order to use
-PySoundFile. Additionally, You need the library libsndfile installed on
-your computer. On Unix, use your package manager to install libsndfile.
-Then just install PySoundFile using pip or `python setup.py install`.
-
-If you are running Windows, I recommend using [WinPython][] or some
-similar distribution. This should set you up with Numpy. However, you
-also need CFFI and it's dependency, PyCParser. A good place to get
-these are the [Unofficial Windows Binaries for Python][pybuilds].
-Having installed those, you can download the Windows installers for
-PySoundFile:
-
-[PySoundFile-0.5.0.win-amd64-py2.7](https://github.com/bastibe/PySoundFile/releases/download/0.5.0/PySoundFile-0.5.0.win-amd64-py2.7.exe)
-[PySoundFile-0.5.0.win-amd64-py3.3](https://github.com/bastibe/PySoundFile/releases/download/0.5.0/PySoundFile-0.5.0.win-amd64-py3.3.exe)
-[PySoundFile-0.5.0.win32-py2.7](https://github.com/bastibe/PySoundFile/releases/download/0.5.0/PySoundFile-0.5.0.win32-py2.7.exe)
-[PySoundFile-0.5.0.win32-py3.3](https://github.com/bastibe/PySoundFile/releases/download/0.5.0/PySoundFile-0.5.0.win32-py3.3.exe)
-
-[WinPython]: https://code.google.com/p/winpython/
-[pybuilds]: http://www.lfd.uci.edu/~gohlke/pythonlibs/
-
-Usage
------
-
-Each SoundFile can either open a sound file on the disk, or a
-file-like object (using `libsndfile`'s [virtual file interface][vio]).
-Every sound file has a specific samplerate, data format and a set
-number of channels. Each sound file can be opened in `read_mode`,
-`write_mode`, or `read_write_mode`. Note that `read_write_mode` is
-unsupported for some formats.
-
-You can read and write any file that [`libsndfile`][formats] can open.
-This includes Microsoft WAV, OGG, FLAC and Matlab MAT files. Different
-variants of these can be built by ORing `snd_types`, `snd_subtypes`
-and `snd_endians` or using the predefined formats `wave_file`,
-`flac_file`, `matlab_file` and `ogg_file`. Note that specifying the
-format is only necessary when writing.
-
-If a file on disk is opened, it is kept open for as long as the
-SoundFile object exists and closes automatically when it goes out of
-scope. Alternatively, the SoundFile object can be used as a context
-manager, which closes the file when it exits.
-
-All data access uses frames as index. A frame is one discrete
-time-step in the sound file. Every frame contains as many samples as
-there are channels in the file.
-
-[vio]: http://www.mega-nerd.com/libsndfile/api.html#open_virtual
-[formats]: http://www.mega-nerd.com/libsndfile/#Features
-
-### Read/Write Functions
-
-Data can be written to the file using `write()`, or read from the
-file using `read()`. Every read and write operation starts at a
-certain position in the file. Reading N frames will change this
-position by N frames as well. Alternatively, `seek()`, and
-`seek_absolute()`, can be used to set the current position to a
-frame index offset from the current position, the start of the file,
-or the end of the file, respectively.
-
-Here is an example for a program that reads a wave file and copies it
-into an ogg-vorbis file:
-
-```python
-from pysoundfile import SoundFile
-
-wave = SoundFile('existing_file.wav')
-ogg = SoundFile('new_file.ogg', sample_rate=wave.sample_rate,
- channels=wave.channels, format=ogg_file,
- mode=write_mode)
-
-data = wave.read(1024)
-while len(data) > 0:
- ogg.write(data)
- data = wave.read(1024)
-```
-
-### Sequence Interface
-
-Alternatively, slices can be used to access data at arbitrary
-positions in the file. If you index in two dimensions, you can select
-single channels of a multi-channel file.
-
-Here is an example of reading in a whole wave file into a NumPy array:
-
-```python
-from pysoundfile import SoundFile
-
-wave = SoundFile('existing_file.wav')[:]
-```
-
-### Virtual IO
-
-If you have an open file-like object, you can use something
-similar to this to decode it:
-
-```python
-from io import BytesIO
-from pysoundfile import SoundFile
-fObj = BytesIO(open('filename.flac', 'rb').read())
-flac = SoundFile(fObj, virtual_io=True)
-```
-
-Here is an example using an HTTP request:
-```python
-from io import BytesIO
-from pysoundfile import SoundFile
-import requests
-
-fObj = BytesIO()
-response = requests.get('http://www.example.com/my.flac', stream=True)
-for data in response.iter_content(4096):
- if data:
- fObj.write(data)
-fObj.seek(0)
-flac = SoundFile(fObj, virtual_io=True)
-```
-
-### Accessing Text Data
-
-In addition to audio data, there are a number of text fields in every
-sound file. In particular, you can set a title, a copyright notice, a
-software description, the artist name, a comment, a date, the album
-name, a license, a tracknumber and a genre. Note however, that not all
-of these fields are supported for every file format.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..bc9eaeb
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,112 @@
+PySoundFile
+===========
+
+PySoundFile is an audio library based on libsndfile, CFFI and Numpy
+
+PySoundFile can read and write sound files. File reading/writing is
+supported through `libsndfile `__,
+which is a free, cross-platform, open-source library for reading and
+writing many different sampled sound file formats that runs on many
+platforms including Windows, OS X, and Unix. It is accessed through
+`CFFI `__, which is a foreign function
+interface for Python calling C code. CFFI is supported for CPython 2.6+,
+3.x and PyPy 2.0+. PySoundFile represents audio data as NumPy arrays.
+
+| PySoundFile is BSD licensed.
+| (c) 2013, Bastian Bechtold
+
+Installation
+------------
+
+On the Python side, you need to have CFFI and Numpy in order to use
+PySoundFile. Additionally, You need the library libsndfile installed on
+your computer. On Unix, use your package manager to install libsndfile.
+Then just install PySoundFile using pip or ``python setup.py install``.
+
+If you are running Windows, I recommend using
+`WinPython `__ or some similar
+distribution. This should set you up with Numpy. However, you also need
+CFFI and it's dependency, PyCParser. A good place to get these are the
+`Unofficial Windows Binaries for
+Python `__. Having installed
+those, you can download the Windows installers for PySoundFile:
+
+| `PySoundFile-0.5.0.win-amd64-py2.7 `__
+| `PySoundFile-0.5.0.win-amd64-py3.3 `__
+| `PySoundFile-0.5.0.win32-py2.7 `__
+| `PySoundFile-0.5.0.win32-py3.3 `__
+
+Usage
+-----
+
+Each SoundFile can either open a sound file on the disk, or a file-like
+object (using ``libsndfile``'s `virtual file
+interface `__).
+Every sound file has a specific samplerate, data format and a set number
+of channels.
+
+You can read and write any file that
+`libsndfile `__ can
+open. This includes Microsoft WAV, OGG, FLAC and Matlab MAT files.
+
+If a file on disk is opened, it is kept open for as long as the
+SoundFile object exists and closes automatically when it goes out of
+scope. Alternatively, the SoundFile object can be used as a context
+manager, which closes the file when it exits.
+
+All data access uses frames as index. A frame is one discrete time-step
+in the sound file. Every frame contains as many samples as there are
+channels in the file.
+
+Read/Write Functions
+~~~~~~~~~~~~~~~~~~~~
+
+Data can be written to the file using ``write()``, or read from the
+file using ``read()``.
+
+Here is an example for a program that reads a wave file and copies it
+into an ogg-vorbis file:
+
+.. code:: python
+
+ import pysoundfile as sf
+
+ data, samplerate = sf.read('existing_file.wav')
+ sf.write(data, 'new_file.ogg', samplerate=samplerate)
+
+Virtual IO
+~~~~~~~~~~
+
+If you have an open file-like object, you can use something similar to
+this to decode it:
+
+.. code:: python
+
+ from pysoundfile import SoundFile
+ with SoundFile('filename.flac', 'rb') as fObj:
+ data, samplerate = sf.read(fObj)
+
+Here is an example using an HTTP request:
+
+.. code:: python
+
+ from io import BytesIO
+ import pysoundfile as sf
+ import requests
+
+ fObj = BytesIO()
+ response = requests.get('http://www.example.com/my.flac', stream=True)
+ for data in response.iter_content(4096):
+ if data:
+ fObj.write(data)
+ fObj.seek(0)
+ data, samplerate = sf.read(fObj)
+
+Accessing Text Data
+~~~~~~~~~~~~~~~~~~~
+
+In addition to audio data, there are a number of text fields in every
+sound file. In particular, you can set a title, a copyright notice, a
+software description, the artist name, a comment, a date, the album
+name, a license, a tracknumber and a genre. Note however, that not all
+of these fields are supported for every file format.
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..0ed7dd4
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PySoundFile.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PySoundFile.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/PySoundFile"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PySoundFile"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..3dfe0e2
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# PySoundFile documentation build configuration file, created by
+# sphinx-quickstart on Sun Sep 21 19:26:48 2014.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+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('.'))
+sys.path.insert(0, os.path.abspath('..'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.mathjax',
+ 'sphinx.ext.viewcode',
+ 'sphinxcontrib.napoleon',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'PySoundFile'
+copyright = '2014, Bastian Bechtold, Matthias Geier'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.5.0'
+# The full version, including alpha/beta/rc tags.
+release = '0.5.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PySoundFiledoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ ('index', 'PySoundFile.tex', 'PySoundFile Documentation',
+ 'Bastian Bechtold, Matthias Geier', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'pysoundfile', 'PySoundFile Documentation',
+ ['Bastian Bechtold, Matthias Geier'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'PySoundFile', 'PySoundFile Documentation',
+ 'Bastian Bechtold, Matthias Geier', 'PySoundFile', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+autodoc_member_order = 'bysource'
+autoclass_content = "init"
+napoleon_use_rtype = False
+napoleon_include_private_with_doc = False
+napoleon_include_special_with_doc = False
+
+# Fake imports to avoid actually loading NumPy and libsndfile
+import fake_numpy
+sys.modules['numpy'] = sys.modules['fake_numpy']
+import fake_cffi
+sys.modules['cffi'] = sys.modules['fake_cffi']
diff --git a/doc/fake_cffi.py b/doc/fake_cffi.py
new file mode 100644
index 0000000..66ed279
--- /dev/null
+++ b/doc/fake_cffi.py
@@ -0,0 +1,11 @@
+"""Mock module for Sphinx autodoc."""
+
+
+class FFI(object):
+ def cdef(self, _):
+ pass
+
+ def dlopen(self, _):
+ return self
+
+ SFC_GET_FORMAT_INFO = NotImplemented
diff --git a/doc/fake_numpy.py b/doc/fake_numpy.py
new file mode 100644
index 0000000..9279494
--- /dev/null
+++ b/doc/fake_numpy.py
@@ -0,0 +1,5 @@
+"""Mock module for Sphinx autodoc."""
+
+
+def dtype(_):
+ return NotImplemented
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..872f87f
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,13 @@
+.. include:: ../README.rst
+
+API Documentation
+=================
+
+.. automodule:: pysoundfile
+ :members:
+ :undoc-members:
+
+Index
+=====
+
+* :ref:`genindex`
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..e1b3da6
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,4 @@
+jinja2
+pygments
+sphinx
+sphinxcontrib-napoleon
diff --git a/pysoundfile.py b/pysoundfile.py
index 028d5da..949e59e 100644
--- a/pysoundfile.py
+++ b/pysoundfile.py
@@ -1,3 +1,15 @@
+"""PySoundFile is an audio library based on libsndfile, CFFI and NumPy.
+
+Sound files can be read or written directly using the functions
+:func:`read` and :func:`write`.
+To read a sound file in a block-wise fashion, use :func:`blocks`.
+Alternatively, sound files can be opened as :class:`SoundFile` objects.
+
+For further information, see http://pysoundfile.rtfd.org/.
+
+"""
+__version__ = "0.5.0"
+
import numpy as _np
from cffi import FFI as _FFI
from os import SEEK_SET, SEEK_CUR, SEEK_END
@@ -7,46 +19,6 @@
except ImportError:
import __builtin__ as _builtins # for Python < 3.0
-__version__ = "0.5.0"
-
-"""PySoundFile is an audio library based on libsndfile, CFFI and Numpy
-
-PySoundFile can read and write sound files. File reading/writing is
-supported through libsndfile[1], which is a free, cross-platform,
-open-source library for reading and writing many different sampled
-sound file formats that runs on many platforms including Windows, OS
-X, and Unix. It is accessed through CFFI[2], which is a foreight
-function interface for Python calling C code. CFFI is supported for
-CPython 2.6+, 3.x and PyPy 2.0+. PySoundFile represents audio data as
-NumPy arrays.
-
-[1]: http://www.mega-nerd.com/libsndfile/
-[2]: http://cffi.readthedocs.org/
-
-Every sound file is represented as a SoundFile object. SoundFiles can
-be created for reading, writing, or both. Each SoundFile has a
-samplerate, a number of channels, and a file format. These can not be
-changed at runtime.
-
-A SoundFile has methods for reading and writing data to/from the file.
-Even though every sound file has a fixed file format, reading and
-writing is possible in four different NumPy formats: int16, int32,
-float32 and float64.
-
-At the same time, SoundFiles act as sequence types, so you can use
-slices to read or write data as well. Since there is no way of
-specifying data formats for slices, the SoundFile will always return
-float64 data for those.
-
-Note that you need to have libsndfile installed in order to use
-PySoundFile. On Windows, you need to rename the library to
-"sndfile.dll".
-
-PySoundFile is BSD licensed.
-(c) 2013, Bastian Bechtold
-
-"""
-
_ffi = _FFI()
_ffi.cdef("""
enum
@@ -272,38 +244,296 @@
_snd = _ffi.dlopen('sndfile')
+def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=True,
+ fill_value=None, out=None, samplerate=None, channels=None,
+ format=None, subtype=None, endian=None, closefd=True):
+ """Provide audio data from a sound file as NumPy array.
+
+ By default, the whole file is read from the beginning, but the
+ position to start reading can be specified with `start` and the
+ number of frames to read can be specified with `frames`.
+ Alternatively, a range can be specified with `start` and `stop`.
+
+ If there is less data left in the file than requested, the rest of
+ the frames are filled with `fill_value`.
+ If no `fill_value` is specified, a smaller array is returned.
+
+ Parameters
+ ----------
+ file : str or int or file-like object
+ The file to read from. See :class:`SoundFile` for details.
+ frames : int, optional
+ The number of frames to read. If `frames` is negative, the whole
+ rest of the file is read. Not allowed if `stop` is given.
+ start : int, optional
+ Where to start reading. A negative value counts from the end.
+ stop : int, optional
+ The index after the last frame to be read. A negative value
+ counts from the end. Not allowed if `frames` is given.
+ dtype : {'float64', 'float32', 'int32', 'int16'}, optional
+ Data type of the returned array, by default ``'float64'``.
+ Floating point audio data is typically in the range from
+ ``-1.0`` to ``1.0``. Integer data is in the range from
+ ``-2**15`` to ``2**15-1`` for ``'int16'`` and from ``-2**31`` to
+ ``2**31-1`` for ``'int32'``.
+
+ Returns
+ -------
+ audiodata : numpy.ndarray or type(out)
+ A two-dimensional NumPy array is returned, where the channels
+ are stored along the first dimension, i.e. as columns.
+ A two-dimensional array is returned even if the sound file has
+ only one channel. Use ``always_2d=False`` to return a
+ one-dimensional array in this case.
+
+ If `out` was specified, it is returned. If `out` has more
+ frames than available in the file (or if `frames` is smaller
+ than the length of `out`) and no `fill_value` is given, then
+ only a part of `out` is overwritten and a view containing all
+ valid frames is returned.
+ samplerate : int
+ The sample rate of the audio file.
+
+ Other Parameters
+ ----------------
+ always_2d : bool, optional
+ By default, audio data is always returned as a two-dimensional
+ array, even if the audio file has only one channel.
+ With ``always_2d=False``, reading a mono sound file will return
+ a one-dimensional array.
+ fill_value : float, optional
+ If more frames are requested than available in the file, the
+ rest of the output is be filled with `fill_value`. If
+ `fill_value` is not specified, a smaller array is returned.
+ out : numpy.ndarray or subclass, optional
+ If `out` is specified, the data is written into the given array
+ instead of creating a new array. In this case, the arguments
+ `dtype` and `always_2d` are silently ignored! If `frames` is
+ not given, it is obtained from the length of `out`.
+ samplerate, channels, format, subtype, endian, closefd
+ See :class:`SoundFile`.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> data, samplerate = sf.read('stereo_file.wav')
+ >>> data
+ array([[ 0.71329652, 0.06294799],
+ [-0.26450912, -0.38874483],
+ ...
+ [ 0.67398441, -0.11516333]])
+ >>> samplerate
+ 44100
+
+ """
+ if frames >= 0 and stop is not None:
+ raise TypeError("Only one of {frames, stop} may be used")
+
+ with SoundFile(file, 'r', samplerate, channels,
+ subtype, endian, format, closefd) as f:
+ start, frames = _get_read_range(frames, start, stop, f.frames)
+ f.seek(start, SEEK_SET)
+ data = f.read(frames, dtype, always_2d, fill_value, out)
+ return data, f.samplerate
+
+
+def write(data, file, samplerate,
+ subtype=None, endian=None, format=None, closefd=True):
+ """Write data to a sound file.
+
+ .. note:: If `file` exists, it will be truncated and overwritten!
+
+ Parameters
+ ----------
+ data : array_like
+ The data to write. Usually two-dimensional (channels x frames),
+ but one-dimensional `data` can be used for mono files.
+ Only the data types ``'float64'``, ``'float32'``, ``'int32'``
+ and ``'int16'`` are supported.
+
+ .. note:: The data type of `data` does **not** select the data
+ type of the written file.
+ Audio data will be converted to the given `subtype`.
+
+ file : str or int or file-like object
+ The file to write to. See :class:`SoundFile` for details.
+ samplerate : int
+ The sample rate of the audio data.
+ subtype : str, optional
+ See :func:`default_subtype` for the default value and
+ :func:`available_subtypes` for all possible values.
+
+ Other Parameters
+ ----------------
+ format, endian, closefd
+ See :class:`SoundFile`.
+
+ Examples
+ --------
+
+ Write 10 frames of random data to a file:
+
+ >>> import numpy as np
+ >>> import pysoundfile as sf
+ >>> sf.write(np.random.randn(10, 2), 'stereo_file.wav', 44100, 'PCM_24')
+
+ """
+ data = _np.asarray(data)
+ if data.ndim == 1:
+ channels = 1
+ else:
+ channels = data.shape[1]
+ with SoundFile(file, 'w', samplerate, channels,
+ subtype, endian, format, closefd) as f:
+ f.write(data)
+
+
+def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None,
+ dtype='float64', always_2d=True, fill_value=None, out=None,
+ samplerate=None, channels=None,
+ format=None, subtype=None, endian=None, closefd=True):
+ """Return a generator for block-wise reading.
+
+ By default, iteration starts at the beginning and stops at the end
+ of the file. Use `start` to start at a later position and `frames`
+ or `stop` to stop earlier.
+
+ If you stop iterating over the generator before it's exhausted,
+ the sound file is not closed. This is normally not a problem
+ because the file is opened in read-only mode. To close the file
+ properly, the generator's ``close()`` method can be called.
+
+ Parameters
+ ----------
+ file : str or int or file-like object
+ The file to read from. See :class:`SoundFile` for details.
+ blocksize : int
+ The number of frames to read per block.
+ Either this or `out` must be given.
+ overlap : int, optional
+ The number of frames to rewind between each block.
+
+ Yields
+ ------
+ numpy.ndarray or type(out)
+ Blocks of audio data.
+ If `out` was given, and the requested frames are not an integer
+ multiple of the length of `out`, and no `fill_value` was given,
+ the last block will be a smaller view into `out`.
+
+ Other Parameters
+ ----------------
+ frames, start, stop
+ See :func:`read`.
+ dtype : {'float64', 'float32', 'int32', 'int16'}, optional
+ See :func:`read`.
+ always_2d, fill_value, out
+ See :func:`read`.
+ samplerate, channels, format, subtype, endian, closefd
+ See :class:`SoundFile`.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> for block in sf.blocks('stereo_file.wav', blocksize=1024):
+ >>> pass # do something with 'block'
+
+ """
+ if frames >= 0 and stop is not None:
+ raise TypeError("Only one of {frames, stop} may be used")
+
+ with SoundFile(file, 'r', samplerate, channels,
+ subtype, endian, format, closefd) as f:
+ start, frames = _get_read_range(frames, start, stop, f.frames)
+ f.seek(start, SEEK_SET)
+ for block in f.blocks(blocksize, overlap, frames,
+ dtype, always_2d, fill_value, out):
+ yield block
+
+
+def available_formats():
+ """Return a dictionary of available major formats.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> sf.available_formats()
+ {'FLAC': 'FLAC (FLAC Lossless Audio Codec)',
+ 'OGG': 'OGG (OGG Container format)',
+ 'WAV': 'WAV (Microsoft)',
+ 'AIFF': 'AIFF (Apple/SGI)',
+ ...
+ 'WAVEX': 'WAVEX (Microsoft)',
+ 'RAW': 'RAW (header-less)',
+ 'MAT5': 'MAT5 (GNU Octave 2.1 / Matlab 5.0)'}
+
+ """
+ return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT,
+ _snd.SFC_GET_FORMAT_MAJOR))
+
+
+def available_subtypes(format=None):
+ """Return a dictionary of available subtypes.
+
+ Parameters
+ ----------
+ format : str
+ If given, only compatible subtypes are returned.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> sf.available_subtypes('FLAC')
+ {'PCM_24': 'Signed 24 bit PCM',
+ 'PCM_16': 'Signed 16 bit PCM',
+ 'PCM_S8': 'Signed 8 bit PCM'}
+
+ """
+ subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT,
+ _snd.SFC_GET_FORMAT_SUBTYPE)
+ return dict((subtype, name) for subtype, name in subtypes
+ if format is None or format_check(format, subtype))
+
+
+def format_check(format, subtype=None, endian=None):
+ """Check if the combination of format/subtype/endian is valid.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> sf.format_check('WAV', 'PCM_24')
+ True
+ >>> sf.format_check('FLAC', 'VORBIS')
+ False
+
+ """
+ try:
+ return bool(_format_int(format, subtype, endian))
+ except (ValueError, TypeError):
+ return False
+
+
+def default_subtype(format):
+ """Return the default subtype for a given format.
+
+ Examples
+ --------
+ >>> import pysoundfile as sf
+ >>> sf.default_subtype('WAV')
+ 'PCM_16'
+ >>> sf.default_subtype('MAT5')
+ 'DOUBLE'
+
+ """
+ return _default_subtypes.get(str(format).upper())
+
+
class SoundFile(object):
- """SoundFile handles reading and writing to sound files.
-
- Each SoundFile opens one sound file on the disk. This sound file
- has a specific samplerate, data format and a set number of
- channels. Each sound file can be opened for reading, for writing or
- both. Note that the latter is unsupported for some formats.
-
- Data can be written to the file using write(), or read from the
- file using read(). Every read and write operation starts at a
- certain position in the file. Reading N frames will change this
- position by N frames as well. Alternatively, seek()
- can be used to set the current position to a frame
- index offset from the current position, the start of the file, or
- the end of the file, respectively.
-
- Alternatively, slices can be used to access data at arbitrary
- positions in the file. Note that slices currently only work on
- frame indices, not channels. The quickest way to read in a whole
- file as a float64 NumPy array is in fact SoundFile('filename')[:].
-
- All data access uses frames as index. A frame is one discrete
- time-step in the sound file. Every frame contains as many samples
- as there are channels in the file.
-
- In addition to audio data, there are a number of text fields in
- every sound file. In particular, you can set a title, a copyright
- notice, a software description, the artist name, a comment, a
- date, the album name, a license, a tracknumber and a genre. Note
- however, that not all of these fields are supported for every file
- format.
+ """A sound file.
+
+ For more documentation see the __init__() docstring (which is also
+ used for the online documentation (http://pysoundfile.rtfd.org/).
"""
@@ -311,25 +541,94 @@ def __init__(self, file, mode='r', samplerate=None, channels=None,
subtype=None, endian=None, format=None, closefd=True):
"""Open a sound file.
- If a file is opened with mode 'r' (the default) or 'r+',
- no samplerate, channels or file format need to be given. If a
- file is opened with another mode, you must provide a samplerate,
- a number of channels, and a file format. An exception is the
- RAW data format, which requires these data points for reading
- as well.
+ If a file is opened with `mode` ``'r'`` (the default) or
+ ``'r+'``, no sample rate, channels or file format need to be
+ given because the information is obtained from the file. An
+ exception is the ``'RAW'`` data format, which always requires
+ these data points.
File formats consist of three case-insensitive strings:
- - a "major format" which is by default obtained from the
- extension of the file name (if known) and which can be
- forced with the format argument (e.g. format='WAVEX').
- - a "subtype", e.g. 'PCM_24'. Most major formats have a default
- subtype which is used if no subtype is specified.
- - an "endian-ness": 'FILE' (default), 'LITTLE', 'BIG' or 'CPU'.
- In most cases this doesn't have to be specified.
-
- The functions available_formats() and available_subtypes() can
- be used to obtain a list of all avaliable major formats and
- subtypes, respectively.
+
+ * a *major format* which is by default obtained from the
+ extension of the file name (if known) and which can be
+ forced with the format argument (e.g. ``format='WAVEX'``).
+ * a *subtype*, e.g. ``'PCM_24'``. Most major formats have a
+ default subtype which is used if no subtype is specified.
+ * an *endian-ness*, which doesn't have to be specified at all in
+ most cases.
+
+ A :class:`SoundFile` object is a *context manager*, which means
+ if used in a "with" statement, :meth:`.close` is automatically
+ called when reaching the end of the code block inside the "with"
+ statement.
+
+ Parameters
+ ----------
+ file : str or int or file-like object
+ The file to open. This can be a file name, a file
+ descriptor or a Python file object (or a similar object with
+ the methods ``read()``/``readinto()``, ``write()``,
+ ``seek()`` and ``tell()``).
+ mode : {'r', 'r+', 'w', 'w+', 'x', 'x+'}, optional
+ Open mode. Has to begin with one of these three characters:
+ ``'r'`` for reading, ``'w'`` for writing (truncates) or
+ ``'x'`` for writing (but fail if already existing).
+ Additionally, it may contain ``'+'`` to open a file for both
+ reading and writing.
+ The character ``'b'`` for *binary mode* is implied because
+ all sound files have to be opened in this mode.
+
+ .. note:: The modes containing ``'x'`` are only available
+ since Python 3.3!
+
+ samplerate : int
+ The sample rate of the file. If `mode` contains ``'r'``,
+ this is obtained from the file (except for ``'RAW'`` files).
+ channels : int
+ The number of channels of the file.
+ If `mode` contains ``'r'``, this is obtained from the file
+ (except for ``'RAW'`` files).
+ subtype : str, sometimes optional
+ The subtype of the sound file. If `mode` contains ``'r'``,
+ this is obtained from the file (except for ``'RAW'``
+ files), if not, the default value depends on the selected
+ `format` (see :func:`default_subtype`).
+ See :func:`available_subtypes` for all possible subtypes for
+ a given `format`.
+ endian : {'FILE', 'LITTLE', 'BIG', 'CPU'}, sometimes optional
+ The endian-ness of the sound file. If `mode` contains
+ ``'r'``, this is obtained from the file (except for
+ ``'RAW'`` files), if not, the default value is ``'FILE'``,
+ which is correct in most cases.
+ format : str, sometimes optional
+ The major format of the sound file. If `mode` contains
+ ``'r'``, this is obtained from the file (except for
+ ``'RAW'`` files), if not, the default value is determined
+ from the file extension. See :func:`available_formats` for
+ all possible values.
+ closefd : bool, optional
+ Whether to close the file descriptor on :meth:`.close`. Only
+ applicable if the `file` argument is a file descriptor.
+
+ Examples
+ --------
+ >>> from pysoundfile import SoundFile
+
+ Open an existing file for reading:
+
+ >>> myfile = SoundFile('existing_file.wav')
+ >>> # do something with myfile
+ >>> myfile.close()
+
+ Create a new sound file for reading and writing using a with
+ statement:
+
+ >>> with SoundFile('new_file.wav', 'x+', 44100, 2) as myfile:
+ >>> # do something with myfile
+ >>> # ...
+ >>> assert not myfile.closed
+ >>> # myfile.close() is called automatically at the end
+ >>> assert myfile.closed
"""
if mode is None:
@@ -393,91 +692,45 @@ def __init__(self, file, mode='r', samplerate=None, channels=None,
self._handle_error()
if modes.issuperset('r+') and self.seekable():
- # Move write pointer to 0 (like in Python file objects)
+ # Move write position to 0 (like in Python file objects)
self.seek(0)
name = property(lambda self: self._name)
+ """The file name of the sound file."""
mode = property(lambda self: self._mode)
+ """The open mode the sound file was opened with."""
frames = property(lambda self: self._info.frames)
+ """The number of frames in the sound file."""
samplerate = property(lambda self: self._info.samplerate)
+ """The sample rate of the sound file."""
channels = property(lambda self: self._info.channels)
+ """The number of channels in the sound file."""
format = property(
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_TYPEMASK))
+ """The major format of the sound file."""
subtype = property(
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_SUBMASK))
+ """The subtype of data in the the sound file."""
endian = property(
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_ENDMASK))
+ """The endian-ness of the data in the sound file."""
format_info = property(
lambda self: _format_info(self._info.format &
_snd.SF_FORMAT_TYPEMASK)[1])
+ """A description of the major format of the sound file."""
subtype_info = property(
lambda self: _format_info(self._info.format &
_snd.SF_FORMAT_SUBMASK)[1])
+ """A description of the subtype of the sound file."""
sections = property(lambda self: self._info.sections)
+ """The number of sections of the sound file."""
closed = property(lambda self: self._file is None)
+ """Whether the sound file is closed or not."""
# avoid confusion if something goes wrong before assigning self._file:
_file = None
_filestream = None
- def seekable(self):
- """Return True if the file supports seeking."""
- return self._info.seekable == _snd.SF_TRUE
-
- def _init_virtual_io(self, file):
- @_ffi.callback("sf_vio_get_filelen")
- def vio_get_filelen(user_data):
- # first try __len__(), if not available fall back to seek()/tell()
- try:
- size = len(file)
- except TypeError:
- curr = file.tell()
- file.seek(0, SEEK_END)
- size = file.tell()
- file.seek(curr, SEEK_SET)
- return size
-
- @_ffi.callback("sf_vio_seek")
- def vio_seek(offset, whence, user_data):
- file.seek(offset, whence)
- return file.tell()
-
- @_ffi.callback("sf_vio_read")
- def vio_read(ptr, count, user_data):
- # first try readinto(), if not available fall back to read()
- try:
- buf = _ffi.buffer(ptr, count)
- data_read = file.readinto(buf)
- except AttributeError:
- data = file.read(count)
- data_read = len(data)
- buf = _ffi.buffer(ptr, data_read)
- buf[0:data_read] = data
- return data_read
-
- @_ffi.callback("sf_vio_write")
- def vio_write(ptr, count, user_data):
- buf = _ffi.buffer(ptr, count)
- data = buf[:]
- written = file.write(data)
- # write() returns None for file objects in Python <= 2.7:
- if written is None:
- written = count
- return written
-
- @_ffi.callback("sf_vio_tell")
- def vio_tell(user_data):
- return file.tell()
-
- # Note: the callback functions must be kept alive!
- self._virtual_io = {'get_filelen': vio_get_filelen,
- 'seek': vio_seek,
- 'read': vio_read,
- 'write': vio_write,
- 'tell': vio_tell}
-
- return _ffi.new("SF_VIRTUAL_IO*", self._virtual_io)
-
def __del__(self):
self.close()
@@ -487,31 +740,8 @@ def __enter__(self):
def __exit__(self, *args):
self.close()
- def _handle_error(self):
- # this checks the error flag of the SNDFILE* structure
- self._check_if_closed()
- err = _snd.sf_error(self._file)
- self._handle_error_number(err)
-
- def _handle_error_number(self, err):
- # pretty-print a numerical error code
- if err != 0:
- err_str = _snd.sf_error_number(err)
- raise RuntimeError(_ffi.string(err_str).decode())
-
- def _getAttributeNames(self):
- # return all possible attributes used in __setattr__ and __getattr__.
- # This is useful for auto-completion (e.g. IPython)
- return _str_types
-
- def _check_if_closed(self):
- # check if the file is closed and raise an error if it is.
- # This should be used in every method that tries to access self._file.
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
def __setattr__(self, name, value):
- # access text data in the sound file through properties
+ """Write text meta-data in the sound file through properties."""
if name in _str_types:
self._check_if_closed()
data = _ffi.new('char[]', value.encode())
@@ -521,7 +751,7 @@ def __setattr__(self, name, value):
super(SoundFile, self).__setattr__(name, value)
def __getattr__(self, name):
- # access text data in the sound file through properties
+ """Read text meta-data in the sound file through properties."""
if name in _str_types:
self._check_if_closed()
data = _snd.sf_get_string(self._file, _str_types[name])
@@ -532,17 +762,6 @@ def __getattr__(self, name):
def __len__(self):
return self.frames
- def _get_slice_bounds(self, frame):
- # get start and stop index from slice, asserting step==1
- if not isinstance(frame, slice):
- frame = slice(frame, frame + 1)
- start, stop, step = frame.indices(len(self))
- if step != 1:
- raise RuntimeError("Step size must be 1")
- if start > stop:
- stop = start
- return start, stop
-
def __getitem__(self, frame):
# access the file as if it where a Numpy array. The data is
# returned as numpy array.
@@ -577,108 +796,92 @@ def __setitem__(self, frame, data):
self.seek(curr, SEEK_SET)
return data
- def flush(self):
- """Write unwritten data to disk."""
- self._check_if_closed()
- _snd.sf_write_sync(self._file)
-
- def close(self):
- """Close the file. Can be called multiple times."""
- if not self.closed:
- # be sure to flush data to disk before closing the file
- self.flush()
- err = _snd.sf_close(self._file)
- self._file = None
- if self._filestream:
- self._filestream.close()
- self._handle_error_number(err)
+ def seekable(self):
+ """Return True if the file supports seeking."""
+ return self._info.seekable == _snd.SF_TRUE
def seek(self, frames, whence=SEEK_SET):
- """Set the read and/or write position.
-
- By default (whence=SEEK_SET), frames are counted from the
- beginning of the file. SEEK_CUR seeks from the current position
- (positive and negative values are allowed).
- SEEK_END seeks from the end (use negative values).
+ """Set the read/write position.
- If the file is opened in 'rw' mode, both read and write position
- are set to the same value by default.
- Use which='r' or which='w' to set only the read position or the
- write position, respectively.
+ Parameters
+ ----------
+ frames : int
+ The frame index or offset to seek.
+ whence : {SEEK_SET, SEEK_CUR, SEEK_END}, optional
+ By default (``whence=SEEK_SET``), `frames` are counted from
+ the beginning of the file.
+ ``whence=SEEK_CUR`` seeks from the current position
+ (positive and negative values are allowed for `frames`).
+ ``whence=SEEK_END`` seeks from the end (use negative value
+ for `frames`).
- To set the read/write position to the beginning of the file,
- use seek(0), to set it to right after the last frame,
- e.g. for appending new data, use seek(0, SEEK_END).
+ Returns
+ -------
+ int
+ The new absolute read/write position in frames, or a
+ negative value on error.
- Returns the new absolute read/write position in frames or a
- negative value on error.
+ Examples
+ --------
+ >>> from pysoundfile import SoundFile, SEEK_END
+ >>> myfile = SoundFile('stereo_file.wav')
- """
- self._check_if_closed()
- return _snd.sf_seek(self._file, frames, whence)
+ Seek to the beginning of the file:
- def _check_array(self, array):
- # Do some error checking
- if (array.ndim not in (1, 2) or
- array.ndim == 1 and self.channels != 1 or
- array.ndim == 2 and array.shape[1] != self.channels):
- raise ValueError("Invalid shape: %s" % repr(array.shape))
+ >>> myfile.seek(0)
+ 0
- if array.dtype not in _ffi_types:
- raise ValueError("dtype must be one of %s" %
- repr([dt.name for dt in _ffi_types]))
+ Seek to the end of the file:
- def _create_empty_array(self, frames, always_2d, dtype):
- # Create an empty array with appropriate shape
- if always_2d or self.channels > 1:
- shape = frames, self.channels
- else:
- shape = frames,
- return _np.empty(shape, dtype, order='C')
+ >>> myfile.seek(0, SEEK_END)
+ 44100 # this is the file length
- def _read_or_write(self, funcname, array, frames):
- # Call into libsndfile
+ """
self._check_if_closed()
-
- ffi_type = _ffi_types[array.dtype]
- assert array.flags.c_contiguous
- assert array.dtype.itemsize == _ffi.sizeof(ffi_type)
- assert array.size >= frames * self.channels
-
- if self.seekable():
- curr = self.seek(0, SEEK_CUR)
- func = getattr(_snd, funcname + ffi_type)
- ptr = _ffi.cast(ffi_type + '*', array.ctypes.data)
- frames = func(self._file, ptr, frames)
- self._handle_error()
- if self.seekable():
- self.seek(curr + frames, SEEK_SET) # Update read & write position
- return frames
+ return _snd.sf_seek(self._file, frames, whence)
def read(self, frames=-1, dtype='float64', always_2d=True,
fill_value=None, out=None):
- """Read a number of frames from the file.
-
- Reads the given number of frames in the given data format from
- the current read position. This also advances the read
- position by the same number of frames.
- Use frames=-1 to read until the end of the file.
-
- A two-dimensional NumPy array is returned, where the channels
- are stored along the first dimension, i.e. as columns.
- A two-dimensional array is returned even if the sound file has
- only one channel. Use always_2d=False to return a
- one-dimensional array in this case.
-
- If out is specified, the data is written into the given NumPy
- array. In this case, the arguments dtype and always_2d are
- silently ignored!
-
- If there is less data left in the file than requested, the rest
- of the frames are filled with fill_value. If fill_value=None, a
- smaller array is returned.
- If out is given, only a part of it is overwritten and a view
- containing all valid frames is returned.
+ """Read from the file and return data as NumPy array.
+
+ Reads the given number of frames in the given data format
+ starting at the current read/write position. This advances the
+ read/write position by the same number of frames.
+ By default, all frames from the current read/write position to
+ the end of the file are returned.
+ Use :meth:`.seek` to move the current read/write position.
+
+ Parameters
+ ----------
+ frames : int, optional
+ The number of frames to read. If ``frames < 0``, the whole
+ rest of the file is read.
+ dtype : {'float64', 'float32', 'int32', 'int16'}, optional
+ See :func:`read`.
+
+ Returns
+ -------
+ numpy.ndarray or type(out)
+ The read data; either a new array or `out` or a view into
+ `out`. See :func:`read` for details.
+
+ Other Parameters
+ ----------------
+ always_2d, fill_value, out
+ See :func:`read`.
+
+ Examples
+ --------
+ >>> from pysoundfile import SoundFile
+ >>> myfile = SoundFile('stereo_file.wav')
+
+ Reading 3 frames from a stereo file:
+
+ >>> myfile.read(3)
+ array([[ 0.71329652, 0.06294799],
+ [-0.26450912, -0.38874483],
+ [ 0.67398441, -0.11516333]])
+ >>> myfile.close()
"""
if out is None:
@@ -705,14 +908,16 @@ def read(self, frames=-1, dtype='float64', always_2d=True,
return out
def write(self, data):
- """Write a number of frames to the file.
+ """Write audio data to the file.
- Writes a number of frames to the current write position in the
- file. This also advances the write position by the same number
- of frames and enlarges the file if necessary.
+ Writes a number of frames at the read/write position to the
+ file. This also advances the read/write position by the same
+ number of frames and enlarges the file if necessary.
- The data must be provided as a (frames x channels) NumPy
- array or as one-dimensional array for mono signals.
+ Parameters
+ ----------
+ data : array_like
+ See :func:`write`.
"""
# no copy is made if data has already the correct memory layout:
@@ -728,20 +933,34 @@ def write(self, data):
def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
always_2d=True, fill_value=None, out=None):
- """Return a generator for block-wise processing.
-
- By default, the generator returns blocks of the given blocksize
- until the end of the file is reached, frames can be used to
- stop earlier.
-
- overlap can be used to rewind a certain number of frames between
- blocks.
-
- For the arguments dtype, always_2d, fill_value and out see
- SoundFile.read().
-
- If fill_value is not specified, the last block may be smaller
- than blocksize.
+ """Return a generator for block-wise reading.
+
+ By default, the generator yields blocks of the given
+ `blocksize` (using a given `overlap`) until the end of the file
+ is reached; `frames` can be used to stop earlier.
+
+ Parameters
+ ----------
+ blocksize : int
+ The number of frames to read per block. Either this or `out`
+ must be given.
+ overlap : int, optional
+ The number of frames to rewind between each block.
+ frames : int, optional
+ The number of frames to read.
+ If ``frames < 1``, the file is read until the end.
+ dtype : {'float64', 'float32', 'int32', 'int16'}, optional
+ See :func:`read`.
+
+ Yields
+ ------
+ numpy.ndarray or type(out)
+ Blocks of audio data. See :func:`blocks` for details.
+
+ Other Parameters
+ ----------------
+ always_2d, fill_value, out
+ See :func:`read`.
"""
if 'r' not in self.mode and '+' not in self.mode:
@@ -772,124 +991,167 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
frames += overlap
yield block
+ def flush(self):
+ """Write unwritten data to the file system.
-def open(file, mode='r', samplerate=None, channels=None,
- subtype=None, endian=None, format=None, closefd=True):
- return SoundFile(file, mode, samplerate, channels,
- subtype, endian, format, closefd)
-
-open.__doc__ = SoundFile.__init__.__doc__
-
+ Data written with :meth:`.write` is not immediately written to
+ the file system but buffered in memory to be written at a later
+ time. Calling :meth:`.flush` makes sure that all changes are
+ actually written to the file system.
-def read(file, samplerate=None, channels=None, subtype=None, endian=None,
- format=None, closefd=True, start=0, stop=None, frames=-1,
- dtype='float64', always_2d=True, fill_value=None, out=None):
- """Read a sound file and return its contents as NumPy array.
+ This has no effect on files opened in read-only mode.
- The number of frames to read can be specified with frames, the
- position to start reading can be specified with start.
- By default, the whole file is read from the beginning.
- Alternatively, a range can be specified with start and stop.
- Both start and stop accept negative indices to specify positions
- relative to the end of the file.
+ """
+ self._check_if_closed()
+ _snd.sf_write_sync(self._file)
- A two-dimensional NumPy array is returned, where the channels are
- stored along the first dimension, i.e. as columns.
- A two-dimensional array is returned even if the sound file has only
- one channel.
- Use always_2d=False to return a one-dimensional array in this case.
+ def close(self):
+ """Close the file. Can be called multiple times."""
+ if not self.closed:
+ # be sure to flush data to disk before closing the file
+ self.flush()
+ err = _snd.sf_close(self._file)
+ self._file = None
+ if self._filestream:
+ self._filestream.close()
+ self._handle_error_number(err)
- If out is specified, the data is written into the given NumPy array.
- In this case, the arguments frames, dtype and always_2d are silently
- ignored!
+ def _init_virtual_io(self, file):
+ """Initialize callback functions for sf_open_virtual()."""
+ @_ffi.callback("sf_vio_get_filelen")
+ def vio_get_filelen(user_data):
+ # first try __len__(), if not available fall back to seek()/tell()
+ try:
+ size = len(file)
+ except TypeError:
+ curr = file.tell()
+ file.seek(0, SEEK_END)
+ size = file.tell()
+ file.seek(curr, SEEK_SET)
+ return size
- If there is less data left in the file than requested, the rest of
- the frames are filled with fill_value. If fill_value=None, a smaller
- array is returned.
- If out is given, only a part of it is overwritten and a view
- containing all valid frames is returned.
+ @_ffi.callback("sf_vio_seek")
+ def vio_seek(offset, whence, user_data):
+ file.seek(offset, whence)
+ return file.tell()
- The keyword arguments samplerate, channels, format, subtype and
- endian are only needed for 'RAW' files. See open() for details.
+ @_ffi.callback("sf_vio_read")
+ def vio_read(ptr, count, user_data):
+ # first try readinto(), if not available fall back to read()
+ try:
+ buf = _ffi.buffer(ptr, count)
+ data_read = file.readinto(buf)
+ except AttributeError:
+ data = file.read(count)
+ data_read = len(data)
+ buf = _ffi.buffer(ptr, data_read)
+ buf[0:data_read] = data
+ return data_read
- """
- if frames >= 0 and stop is not None:
- raise TypeError("Only one of {frames, stop} may be used")
+ @_ffi.callback("sf_vio_write")
+ def vio_write(ptr, count, user_data):
+ buf = _ffi.buffer(ptr, count)
+ data = buf[:]
+ written = file.write(data)
+ # write() returns None for file objects in Python <= 2.7:
+ if written is None:
+ written = count
+ return written
- with SoundFile(file, 'r', samplerate, channels,
- subtype, endian, format, closefd) as f:
- start, frames = _get_read_range(start, stop, frames, f.frames)
- f.seek(start, SEEK_SET)
- data = f.read(frames, dtype, always_2d, fill_value, out)
- return data, f.samplerate
+ @_ffi.callback("sf_vio_tell")
+ def vio_tell(user_data):
+ return file.tell()
+ # Note: the callback functions must be kept alive!
+ self._virtual_io = {'get_filelen': vio_get_filelen,
+ 'seek': vio_seek,
+ 'read': vio_read,
+ 'write': vio_write,
+ 'tell': vio_tell}
-def write(data, file, samplerate,
- subtype=None, endian=None, format=None, closefd=True):
- """Write data from a NumPy array into a sound file.
+ return _ffi.new("SF_VIRTUAL_IO*", self._virtual_io)
- If file exists, it will be overwritten!
+ def _handle_error(self):
+ """Check the error flag of the SNDFILE* structure."""
+ self._check_if_closed()
+ err = _snd.sf_error(self._file)
+ self._handle_error_number(err)
- If data is one-dimensional, a mono file is written.
- For two-dimensional data, the columns are interpreted as channels.
+ def _handle_error_number(self, err):
+ """Pretty-print a numerical error code."""
+ if err != 0:
+ err_str = _snd.sf_error_number(err)
+ raise RuntimeError(_ffi.string(err_str).decode())
- All further arguments are forwarded to open().
+ def _getAttributeNames(self):
+ """Return all attributes used in __setattr__ and __getattr__.
- Example usage:
+ This is useful for auto-completion (e.g. IPython).
- import pysoundfile as sf
- sf.write(myarray, 'myfile.wav', 44100, 'PCM_24')
+ """
+ return _str_types
- """
- data = _np.asarray(data)
- if data.ndim == 1:
- channels = 1
- else:
- channels = data.shape[1]
- with open(file, 'w', samplerate, channels,
- subtype, endian, format, closefd) as f:
- f.write(data)
+ def _check_if_closed(self):
+ """Check if the file is closed and raise an error if it is.
+ This should be used in every method that uses self._file.
-def blocks(file, samplerate=None, channels=None,
- subtype=None, endian=None, format=None, closefd=True,
- blocksize=None, overlap=0, start=0, stop=None, frames=-1,
- dtype='float64', always_2d=True, fill_value=None, out=None):
- """Return a generator for block-wise processing.
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
- Example usage:
+ def _get_slice_bounds(self, frame):
+ # get start and stop index from slice, asserting step==1
+ if not isinstance(frame, slice):
+ frame = slice(frame, frame + 1)
+ start, stop, step = frame.indices(len(self))
+ if step != 1:
+ raise RuntimeError("Step size must be 1")
+ if start > stop:
+ stop = start
+ return start, stop
- import pysoundfile as sf
- for block in sf.blocks('myfile.wav', blocksize=128):
- print(block.max())
- # ... or do something more useful with 'block'
+ def _check_array(self, array):
+ """Do some error checking."""
+ if (array.ndim not in (1, 2) or
+ array.ndim == 1 and self.channels != 1 or
+ array.ndim == 2 and array.shape[1] != self.channels):
+ raise ValueError("Invalid shape: %s" % repr(array.shape))
- All keyword arguments of SoundFile.blocks() are allowed.
- All further arguments are forwarded to open().
+ if array.dtype not in _ffi_types:
+ raise ValueError("dtype must be one of %s" %
+ repr([dt.name for dt in _ffi_types]))
- By default, iteration stops at the end of the file. Use frames or
- stop to stop earlier.
+ def _create_empty_array(self, frames, always_2d, dtype):
+ """Create an empty array with appropriate shape."""
+ if always_2d or self.channels > 1:
+ shape = frames, self.channels
+ else:
+ shape = frames,
+ return _np.empty(shape, dtype, order='C')
- If you stop iterating over the generator before it's exhausted, the
- sound file is not closed. This is normally not a problem because
- the file is opened in read-only mode. To close the file properly,
- the generator's close() method can be called.
+ def _read_or_write(self, funcname, array, frames):
+ """Call into libsndfile."""
+ self._check_if_closed()
- """
- if frames >= 0 and stop is not None:
- raise TypeError("Only one of {frames, stop} may be used")
+ ffi_type = _ffi_types[array.dtype]
+ assert array.flags.c_contiguous
+ assert array.dtype.itemsize == _ffi.sizeof(ffi_type)
+ assert array.size >= frames * self.channels
- with open(file, 'r', samplerate, channels,
- subtype, endian, format, closefd) as f:
- start, frames = _get_read_range(start, stop, frames, f.frames)
- f.seek(start, SEEK_SET)
- for block in f.blocks(blocksize, overlap, frames,
- dtype, always_2d, fill_value, out):
- yield block
+ if self.seekable():
+ curr = self.seek(0, SEEK_CUR)
+ func = getattr(_snd, funcname + ffi_type)
+ ptr = _ffi.cast(ffi_type + '*', array.ctypes.data)
+ frames = func(self._file, ptr, frames)
+ self._handle_error()
+ if self.seekable():
+ self.seek(curr + frames, SEEK_SET) # Update read & write position
+ return frames
-def _get_read_range(start, stop, frames, total_frames):
- # Calculate start frame and length
+def _get_read_range(frames, start, stop, total_frames):
+ """Calculate start frame and length."""
start, stop, _ = slice(start, stop).indices(total_frames)
if stop < start:
stop = start
@@ -898,13 +1160,8 @@ def _get_read_range(start, stop, frames, total_frames):
return start, frames
-def default_subtype(format):
- """Return default subtype for given format."""
- return _default_subtypes.get(str(format).upper())
-
-
def _format_int(format, subtype, endian):
- # Return numeric format ID for given format|subtype|endian combo
+ """Return numeric ID for given format|subtype|endian combo."""
try:
result = _formats[str(format).upper()]
except KeyError:
@@ -933,16 +1190,8 @@ def _format_int(format, subtype, endian):
return result
-def format_check(format, subtype=None, endian=None):
- """Check if the combination of format/subtype/endian is valid."""
- try:
- return bool(_format_int(format, subtype, endian))
- except (ValueError, TypeError):
- return False
-
-
def _format_str(format_int):
- # Return the string representation of a given numeric format
+ """Return the string representation of a given numeric format."""
for dictionary in _formats, _subtypes, _endians:
for k, v in dictionary.items():
if v == format_int:
@@ -951,7 +1200,7 @@ def _format_str(format_int):
def _format_info(format_int, format_flag=_snd.SFC_GET_FORMAT_INFO):
- # Return the ID and short description of a given format.
+ """Return the ID and short description of a given format."""
format_info = _ffi.new("SF_FORMAT_INFO*")
format_info.format = format_int
_snd.sf_command(_ffi.NULL, format_flag, format_info,
@@ -962,26 +1211,8 @@ def _format_info(format_int, format_flag=_snd.SFC_GET_FORMAT_INFO):
def _available_formats_helper(count_flag, format_flag):
- # Generator function used in available_formats() and available_subtypes()
+ """Helper for available_formats() and available_subtypes()."""
count = _ffi.new("int*")
_snd.sf_command(_ffi.NULL, count_flag, count, _ffi.sizeof("int"))
for format_int in range(count[0]):
yield _format_info(format_int, format_flag)
-
-
-def available_formats():
- """Return a dictionary of available major formats."""
- return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT,
- _snd.SFC_GET_FORMAT_MAJOR))
-
-
-def available_subtypes(format=None):
- """Return a dictionary of available subtypes.
-
- If format is specified, only compatible subtypes are returned.
-
- """
- subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT,
- _snd.SFC_GET_FORMAT_SUBTYPE)
- return dict((subtype, name) for subtype, name in subtypes
- if format is None or format_check(format, subtype))
diff --git a/setup.py b/setup.py
index cf3d793..d54e48b 100644
--- a/setup.py
+++ b/setup.py
@@ -40,25 +40,5 @@
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Multimedia :: Sound/Audio'
],
- long_description='''
- PySoundFile can read and write sound files.
-
- PySoundFile can read and write sound files. File reading/writing is
- supported through libsndfile_, which is a free, cross-platform,
- open-source library for reading and writing many different sampled
- sound file formats that runs on many platforms including Windows, OS
- X, and Unix. It is accessed through CFFI_, which is a foreign
- function interface for Python calling C code. CFFI is supported for
- CPython 2.6+, 3.x and PyPy 2.0+. PySoundFile represents audio data as
- NumPy arrays.
-
- You must have libsndfile installed in order to use PySoundFile.
-
- .. _libsndfile: http://www.mega-nerd.com/libsndfile/
- .. _CFFI: http://cffi.readthedocs.org/
-
- Note that you need to have libsndfile installed in order to use
- PySoundFile. On Windows, you need to rename the library to
- "sndfile.dll".
-
- ''')
+ long_description=open('README.rst').read(),
+)
diff --git a/tests/test_argspec.py b/tests/test_argspec.py
index 32b950d..9aa987d 100644
--- a/tests/test_argspec.py
+++ b/tests/test_argspec.py
@@ -4,7 +4,6 @@
from inspect import getargspec
-open = getargspec(sf.open)
init = getargspec(sf.SoundFile.__init__)
read_function = getargspec(sf.read)
read_method = getargspec(sf.SoundFile.read)
@@ -26,25 +25,18 @@ def remove_items(collection, subset):
return the_rest
-def test_if_open_is_identical_to_init():
- assert ['self'] + open.args == init.args
- assert open.varargs == init.varargs
- assert open.keywords == init.keywords
- assert open.defaults == init.defaults
-
-
def test_read_defaults():
func_defaults = defaults(read_function)
meth_defaults = defaults(read_method)
- open_defaults = defaults(open)
+ init_defaults = defaults(init)
- del open_defaults['mode'] # Not meaningful in read() function:
+ del init_defaults['mode'] # Not meaningful in read() function:
del func_defaults['start']
del func_defaults['stop']
- # Same default values as open() and SoundFile.read():
- for spec in open_defaults, meth_defaults:
+ # Same default values as SoundFile.__init__() and SoundFile.read():
+ for spec in init_defaults, meth_defaults:
func_defaults = remove_items(func_defaults, spec)
assert not func_defaults # No more arguments should be left
@@ -52,35 +44,34 @@ def test_read_defaults():
def test_write_defaults():
write_defaults = defaults(write_function)
- open_defaults = defaults(open)
+ init_defaults = defaults(init)
- # Same default values as open()
- open_defaults = remove_items(open_defaults, write_defaults)
+ # Same default values as SoundFile.__init__()
+ init_defaults = remove_items(init_defaults, write_defaults)
- del open_defaults['mode'] # mode is always 'w'
- del open_defaults['channels'] # Inferred from data
- del open_defaults['samplerate'] # Obligatory in write()
- assert not open_defaults # No more arguments should be left
+ del init_defaults['mode'] # mode is always 'w'
+ del init_defaults['channels'] # Inferred from data
+ del init_defaults['samplerate'] # Obligatory in write()
+ assert not init_defaults # No more arguments should be left
def test_if_blocks_function_and_method_have_same_defaults():
func_defaults = defaults(blocks_function)
meth_defaults = defaults(blocks_method)
- open_defaults = defaults(open)
+ init_defaults = defaults(init)
del func_defaults['start']
del func_defaults['stop']
- del open_defaults['mode']
+ del init_defaults['mode']
- for spec in open_defaults, meth_defaults:
+ for spec in init_defaults, meth_defaults:
func_defaults = remove_items(func_defaults, spec)
assert not func_defaults
def test_order_of_blocks_arguments():
+ # Only the first few are checked
meth_args = blocks_method.args[1:] # remove 'self'
- meth_args[2:2] = ['start', 'stop']
- open_args = open.args[:]
- open_args.remove('mode')
- assert blocks_function.args == open_args + meth_args
+ meth_args[3:3] = ['start', 'stop']
+ assert blocks_function.args[:10] == ['file'] + meth_args
diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py
index 775ec15..21e88a3 100644
--- a/tests/test_pysoundfile.py
+++ b/tests/test_pysoundfile.py
@@ -80,26 +80,26 @@ def file_wplus(request):
@pytest.yield_fixture
def sf_stereo_r(file_stereo_r):
- with sf.open(file_stereo_r) as f:
+ with sf.SoundFile(file_stereo_r) as f:
yield f
@pytest.yield_fixture
def sf_stereo_w(file_w):
- with sf.open(file_w, 'w', 44100, 2, format='WAV') as f:
+ with sf.SoundFile(file_w, 'w', 44100, 2, format='WAV') as f:
yield f
@pytest.yield_fixture
def sf_stereo_rplus(file_stereo_rplus):
- with sf.open(file_stereo_rplus, 'r+') as f:
+ with sf.SoundFile(file_stereo_rplus, 'r+') as f:
yield f
@pytest.yield_fixture
def sf_stereo_wplus(file_wplus):
- with sf.open(file_wplus, 'w+', 44100, 2,
- format='WAV', subtype='FLOAT') as f:
+ with sf.SoundFile(file_wplus, 'w+', 44100, 2,
+ format='WAV', subtype='FLOAT') as f:
yield f
@@ -316,65 +316,65 @@ def test_blocks_write(sf_stereo_w):
# -----------------------------------------------------------------------------
-# Test open()
+# Test SoundFile.__init__()
# -----------------------------------------------------------------------------
def test_open_with_invalid_file():
with pytest.raises(TypeError) as excinfo:
- sf.open(3.1415)
+ sf.SoundFile(3.1415)
assert "Invalid file" in str(excinfo.value)
def test_open_with_invalid_mode():
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, 42)
+ sf.SoundFile(filename_stereo, 42)
assert "Invalid mode: 42" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_stereo, 'rr')
+ sf.SoundFile(filename_stereo, 'rr')
assert "Invalid mode: 'rr'" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_stereo, 'rw')
+ sf.SoundFile(filename_stereo, 'rw')
assert "exactly one of 'xrw'" in str(excinfo.value)
def test_open_with_more_invalid_arguments():
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_new, 'w', samplerate=3.1415, channels=2)
+ sf.SoundFile(filename_new, 'w', samplerate=3.1415, channels=2)
assert "integer" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_new, 'w', samplerate=44100, channels=3.1415)
+ sf.SoundFile(filename_new, 'w', samplerate=44100, channels=3.1415)
assert "integer" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_new, 'w', 44100, 2, format='WAF')
+ sf.SoundFile(filename_new, 'w', 44100, 2, format='WAF')
assert "Invalid format string" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_new, 'w', 44100, 2, subtype='PCM16')
+ sf.SoundFile(filename_new, 'w', 44100, 2, subtype='PCM16')
assert "Invalid subtype string" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_new, 'w', 44100, 2, endian='BOTH')
+ sf.SoundFile(filename_new, 'w', 44100, 2, endian='BOTH')
assert "Invalid endian-ness" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
- sf.open(filename_stereo, closefd=False)
+ sf.SoundFile(filename_stereo, closefd=False)
assert "closefd=False" in str(excinfo.value)
def test_open_r_and_rplus_with_too_many_arguments():
for mode in 'r', 'r+':
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode, samplerate=44100)
+ sf.SoundFile(filename_stereo, mode, samplerate=44100)
assert "Not allowed" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode, channels=2)
+ sf.SoundFile(filename_stereo, mode, channels=2)
assert "Not allowed" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode, format='WAV')
+ sf.SoundFile(filename_stereo, mode, format='WAV')
assert "Not allowed" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode, subtype='FLOAT')
+ sf.SoundFile(filename_stereo, mode, subtype='FLOAT')
assert "Not allowed" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode, endian='FILE')
+ sf.SoundFile(filename_stereo, mode, endian='FILE')
assert "Not allowed" in str(excinfo.value)
@@ -382,31 +382,31 @@ def test_open_w_and_wplus_with_too_few_arguments():
filename = 'not_existing.xyz'
for mode in 'w', 'w+':
with pytest.raises(TypeError) as excinfo:
- sf.open(filename, mode, samplerate=44100, channels=2)
+ sf.SoundFile(filename, mode, samplerate=44100, channels=2)
assert "No format specified" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename, mode, samplerate=44100, format='WAV')
+ sf.SoundFile(filename, mode, samplerate=44100, format='WAV')
assert "channels" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
- sf.open(filename, mode, channels=2, format='WAV')
+ sf.SoundFile(filename, mode, channels=2, format='WAV')
assert "samplerate" in str(excinfo.value)
def test_open_with_mode_is_none():
with pytest.raises(TypeError) as excinfo:
- sf.open(filename_stereo, mode=None)
+ sf.SoundFile(filename_stereo, mode=None)
assert "Invalid mode: None" in str(excinfo.value)
with open(filename_stereo, 'rb') as fobj:
- with sf.open(fobj, mode=None) as f:
+ with sf.SoundFile(fobj, mode=None) as f:
assert f.mode == 'rb'
@pytest.mark.skipif(PY2, reason="mode='x' not supported in Python 2")
def test_open_with_mode_is_x():
with pytest.raises(FileExistsError):
- sf.open(filename_stereo, 'x', 44100, 2)
+ sf.SoundFile(filename_stereo, 'x', 44100, 2)
with pytest.raises(FileExistsError):
- sf.open(filename_stereo, 'x+', 44100, 2)
+ sf.SoundFile(filename_stereo, 'x+', 44100, 2)
# -----------------------------------------------------------------------------
@@ -597,30 +597,30 @@ def test_rplus_append_data(sf_stereo_rplus):
def test_context_manager_should_open_and_close_file():
- with sf.open(filename_stereo) as f:
+ with sf.SoundFile(filename_stereo) as f:
assert not f.closed
assert f.closed
def test_closing_should_close_file():
- f = sf.open(filename_stereo)
+ f = sf.SoundFile(filename_stereo)
assert not f.closed
f.close()
assert f.closed
def test_file_attributes_should_save_to_disk():
- with sf.open(filename_new, 'w', 44100, 2, format='WAV') as f:
+ with sf.SoundFile(filename_new, 'w', 44100, 2, format='WAV') as f:
f.title = 'testing'
- with sf.open(filename_new) as f:
+ with sf.SoundFile(filename_new) as f:
assert f.title == 'testing'
os.remove(filename_new)
def test_non_file_attributes_should_not_save_to_disk():
- with sf.open(filename_new, 'w', 44100, 2, format='WAV') as f:
+ with sf.SoundFile(filename_new, 'w', 44100, 2, format='WAV') as f:
f.foobar = 'testing'
- with sf.open(filename_new) as f:
+ with sf.SoundFile(filename_new) as f:
with pytest.raises(AttributeError):
f.foobar
os.remove(filename_new)
@@ -632,16 +632,16 @@ def test_non_file_attributes_should_not_save_to_disk():
def test_read_raw_files_should_read_data():
- with sf.open(filename_raw, 'r', 44100, 1, 'PCM_16') as f:
+ with sf.SoundFile(filename_raw, 'r', 44100, 1, 'PCM_16') as f:
assert np.all(f.read(dtype='int16') == data_mono)
def test_read_raw_files_with_too_few_arguments_should_fail():
with pytest.raises(TypeError): # missing everything
- sf.open(filename_raw)
+ sf.SoundFile(filename_raw)
with pytest.raises(TypeError): # missing subtype
- sf.open(filename_raw, samplerate=44100, channels=2)
+ sf.SoundFile(filename_raw, samplerate=44100, channels=2)
with pytest.raises(TypeError): # missing channels
- sf.open(filename_raw, samplerate=44100, subtype='PCM_16')
+ sf.SoundFile(filename_raw, samplerate=44100, subtype='PCM_16')
with pytest.raises(TypeError): # missing samplerate
- sf.open(filename_raw, channels=2, subtype='PCM_16')
+ sf.SoundFile(filename_raw, channels=2, subtype='PCM_16')