Skip to content

Commit

Permalink
Merge pull request #10 from bmcfee/test-and-docs
Browse files Browse the repository at this point in the history
Test and docs
  • Loading branch information
bmcfee committed Apr 19, 2016
2 parents 4c710e6 + 8158479 commit 64c0333
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ plugins = Cython.Coverage
omit =
resampy/setup.py
resampy/__check_build/*
[report]
show_missing = True
10 changes: 9 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@

.. module:: resampy

.. automodule:: core
Core functionality
------------------
.. automodule:: resampy.core
:members:

Filters
-------
.. automodule:: resampy.filters
:members:

14 changes: 4 additions & 10 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# 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('../resampy'))
sys.path.insert(0, os.path.abspath('../'))

# -- General configuration ------------------------------------------------

Expand Down Expand Up @@ -55,14 +55,9 @@
project = u'resampy'
copyright = u'2016, Brian McFee'

from mock import MagicMock
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return Mock()

MOCK_MODULES = ['numpy', 'scipy', 'scipy.signal']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
import mock
MOCK_MODULES = ['numpy', 'scipy', 'scipy.signal', 'resampy.resample']
sys.modules.update((mod_name, mock.Mock()) for mod_name in MOCK_MODULES)

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down Expand Up @@ -285,4 +280,3 @@ def __getattr__(cls, name):
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
'numpy': ('http://docs.scipy.org/doc/numpy/', None),
'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None)}

101 changes: 101 additions & 0 deletions docs/example.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
.. _examples:

Monophonic resampling
=====================

The following code block demonstrates how to resample an audio signal.

We use `librosa <https://bmcfee.github.io/librosa/>`_ for loading the audio,
but this is purely for ease of demonstration. `resampy` does not depend on `librosa`.

.. code-block:: python
:linenos:
import librosa
import resampy
# Load in librosa's example audio file at its native sampling rate
x, sr_orig = librosa.load(librosa.util.example_audio_file(), sr=None)
# x is now a 1-d numpy array, with `sr_orig` audio samples per second
# We can resample this to any sampling rate we like, say 16000 Hz
y_low = resampy.resample(x, sr_orig, 16000)
# That's it!
Stereo and multi-dimensional data
=================================

The previous example operates on monophonic signals, but resampy also supports stereo
resampling, as demonstrated below.

.. code-block:: python
:linenos:
import librosa
import resampy
# Load in librosa's example audio file at its native sampling rate.
# This time, also disable the stereo->mono downmixing
x, sr_orig = librosa.load(librosa.util.example_audio_file(), sr=None, mono=False)
# x is now a 2-d numpy array, with `sr_orig` audio samples per second
# The first dimension of x indexes the channels, the second dimension indexes
# samples.
# x[0] is the left channel, x[1] is the right channel.
# We can again resample. By default, resample assumes the last index is time.
y_low = resampy.resample(x, sr_orig, 16000)
# To be more explicit, provide a target axis
y_low = resampy.resample(x, sr_orig, 16000, axis=1)
The next block illustrates resampling along an arbitrary dimension.

.. code-block:: python
:linenos:
import numpy as np
import resampy
# Generate 4-dimensional white noise. The third axis (axis=2) will index time.
sr_orig = 22050
x = np.random.randn(10, 3, sr_orig * 5, 2)
# x is now a 10-by-3-by-(5*22050)-by-2 tensor of data.
# We can resample along the time axis as follows
y_low = resampy.resample(x, sr_orig, 11025, axis=2)
# y_low is now a 10-by-3-(5*11025)-by-2 tensor of data
Advanced filtering
==================
resampy allows you to control the design of the filters used in resampling operations.

.. code-block:: python
:linenos:
import numpy as np
import scipy.signal
import librosa
import resampy
# Load in some audio
x, sr_orig = librosa.load(librosa.util.example_audio_file(), sr=None, mono=False)
# Resample to 22050Hz using a Hann-windowed sinc-filter
y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', window=scipy.signal.hann)
# Or a shorter sinc-filter than the default (num_zeros=64)
y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', num_zeros=32)
# Or use the pre-built high-quality filter
y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_best')
# Or use the pre-built fast filter
y = resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast')
30 changes: 29 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,37 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Introduction
------------
`resampy` is a python module for efficient time-series resampling. It is based on the
band-limited sinc interpolation method for sampling rate conversion as described by
[1]_.

.. [1] Smith, Julius O. Digital Audio Resampling Home Page
Center for Computer Research in Music and Acoustics (CCRMA),
Stanford University, 2015-02-23.
Web published at `<http://www-ccrma.stanford.edu/~jos/resample/>`_.
`resampy` supports multi-dimensional resampling on numpy arrays, and is well-suited to
audio applications. For long-duration signals --- e.g., minutes at a high-quality
sampling rate --- `resampy` will be considerably faster
than `scipy.signal.resample` and have little perceivable difference in audio quality.

Its dependencies are `numpy <http://www.numpy.org/>`_, `scipy
<http://www.scipy.org>`_, and `Cython <http://www.cython.org/>`_.


For a quick introduction to using `resampy`, please refer to the `Examples`_ section.

Examples
--------
.. toctree::
:maxdepth: 3

example

API Reference
=============

.. toctree::
:maxdepth: 2

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
numpy>=1.10
scipy>=0.13
six>=1.3
Cython>=0.21
39 changes: 36 additions & 3 deletions resampy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

import numpy as np

from . import filters
from .filters import get_filter
from .resample import resample_f

__all__ = ['resample']


def resample(x, sr_orig, sr_new, axis=-1, filter='sinc_window', **kwargs):
def resample(x, sr_orig, sr_new, axis=-1, filter='kaiser_best', **kwargs):
'''Resample a signal x from sr_orig to sr_new along a given axis.
Parameters
Expand All @@ -30,6 +30,8 @@ def resample(x, sr_orig, sr_new, axis=-1, filter='sinc_window', **kwargs):
filter : optional, str or callable
The resampling filter to use.
By default, uses the `kaiser_best` (pre-computed filter).
kwargs
additional keyword arguments provided to the specified filter
Expand All @@ -42,6 +44,37 @@ def resample(x, sr_orig, sr_new, axis=-1, filter='sinc_window', **kwargs):
------
ValueError
if `sr_orig` or `sr_new` is not positive
Examples
--------
>>> # Generate a sine wave at 440 Hz for 5 seconds
>>> sr_orig = 44100.0
>>> x = np.sin(2 * np.pi * 440.0 / sr_orig * np.arange(5 * sr_orig))
>>> x
array([ 0. , 0.063, ..., -0.125, -0.063])
>>> # Resample to 22050 with default parameters
>>> resampy.resample(x, sr_orig, 22050)
array([ 0.011, 0.123, ..., -0.193, -0.103])
>>> # Resample using the fast (low-quality) filter
>>> resampy.resample(x, sr_orig, 22050, filter='kaiser_fast')
array([ 0.013, 0.121, ..., -0.189, -0.102])
>>> # Resample using a high-quality filter
>>> resampy.resample(x, sr_orig, 22050, filter='kaiser_best')
array([ 0.011, 0.123, ..., -0.193, -0.103])
>>> # Resample using a Hann-windowed sinc filter
>>> resampy.resample(x, sr_orig, 22050, filter='sinc_window',
... window=scipy.signal.hann)
array([ 0.011, 0.123, ..., -0.193, -0.103])
>>> # Generate stereo data
>>> x_right = np.sin(2 * np.pi * 880.0 / sr_orig * np.arange(len(x)))])
>>> x_stereo = np.stack([x, x_right])
>>> x_stereo.shape
(2, 220500)
>>> # Resample along the time axis (1)
>>> y_stereo = resampy.resample(x, sr_orig, 22050, axis=1)
>>> y_stereo.shape
(2, 110250)
'''

if sr_orig <= 0:
Expand All @@ -58,7 +91,7 @@ def resample(x, sr_orig, sr_new, axis=-1, filter='sinc_window', **kwargs):

y = np.zeros(shape, dtype=x.dtype)

interp_win, precision = filters.get_filter(filter, **kwargs)
interp_win, precision = get_filter(filter, **kwargs)

if sample_ratio < 1:
interp_win *= sample_ratio
Expand Down
47 changes: 45 additions & 2 deletions resampy/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''Filter construction and loading'''
'''Filter construction and loading.
--------------------------------
`resampy` provides two pre-computed resampling filters which are tuned for either
high-quality or fast calculation:
- `kaiser_best` : 64 zero-crossings, a Kaiser window with beta=14.769656459379492,
and a roll-off frequency of Nyquist * 0.9475937167399596.
- `kaiser_fast` : 16 zero-crossings, a Kaiser window with beta=8.555504641634386,
and a roll-off frequency of Nyquist * 0.85.
These filters can be used by calling `resample` as follows:
>>> resampy.resample(x, sr_orig, sr_new, filter='kaiser_best') # High-quality
>>> resampy.resample(x, sr_orig, sr_new, filter='kaiser_fast') # Fast calculation
It is also possible to construct custom filters as follows:
>>> resampy.resample(x, sr_orig, sr_new, filter='sinc_window',
... **kwargs)
where ``**kwargs`` are additional parameters to `resampy.filters.sinc_window`_.
'''

import scipy.signal
import numpy as np
Expand Down Expand Up @@ -47,6 +71,21 @@ def sinc_window(num_zeros=64, precision=9, window=None, rolloff=0.945):
if `num_zeros < 1`, `precision < 1`,
or `rolloff` is outside the range `(0, 1]`.
Examples
--------
>>> # A filter with 10 zero-crossings, 32 samples per crossing, and a
... # Hann window for tapering.
>>> halfwin, prec = resampy.filters.sinc_window(num_zeros=10, precision=5,
... window=scipy.signal.hann)
>>> halfwin
array([ 9.450e-01, 9.436e-01, ..., -7.455e-07, -0.000e+00])
>>> prec
32
>>> # Or using sinc-window filter construction directly in resample
>>> y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window',
... num_zeros=10, precision=5,
... window=scipy.signal.hann)
'''

if window is None:
Expand Down Expand Up @@ -86,11 +125,15 @@ def get_filter(name_or_function, **kwargs):
If a function, returns `name_or_function(**kwargs)`.
If a string, and it matches the name of one of the defined
filter functions, the corresponding function is called with **kwargs.
filter functions, the corresponding function is called with `**kwargs`.
If a string, and it matches the name of a pre-computed filter,
the corresponding filter is retrieved, and kwargs is ignored.
Valid pre-computed filter names are:
- 'kaiser_fast'
- 'kaiser_best'
Returns
-------
half_window : np.ndarray
Expand Down
4 changes: 2 additions & 2 deletions resampy/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# -*- coding: utf-8 -*-
"""Version info"""

short_version = '0.0'
version = '0.0.0'
short_version = '0.1'
version = '0.1.0pre'
10 changes: 5 additions & 5 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_bad_rolloff():
def __test(rolloff):

x = np.zeros(100)
resampy.resample(x, 100, 50, rolloff=rolloff)
resampy.resample(x, 100, 50, filter='sinc_window', rolloff=rolloff)

yield __test, -1
yield __test, 1.5
Expand All @@ -51,14 +51,14 @@ def __test(rolloff):
def test_bad_precision():

x = np.zeros(100)
resampy.resample(x, 100, 50, precision=-1)
resampy.resample(x, 100, 50, filter='sinc_window', precision=-1)


@raises(ValueError)
def test_bad_num_zeros():

x = np.zeros(100)
resampy.resample(x, 100, 50, num_zeros=0)
resampy.resample(x, 100, 50, filter='sinc_window', num_zeros=0)

def test_dtype():

Expand All @@ -78,7 +78,7 @@ def test_bad_window():

x = np.zeros(100)

resampy.resample(x, 100, 200, window=np.ones(50))
resampy.resample(x, 100, 200, filter='sinc_window', window=np.ones(50))


def test_good_window():
Expand All @@ -87,6 +87,6 @@ def test_good_window():
sr_new = 200
x = np.random.randn(500)

y = resampy.resample(x, sr_orig, sr_new, window=scipy.signal.blackman)
y = resampy.resample(x, sr_orig, sr_new, filter='sinc_window', window=scipy.signal.blackman)

eq_(len(y), 2 * len(x))

0 comments on commit 64c0333

Please sign in to comment.