Skip to content

Commit

Permalink
Merge a3fbbf8 into 98469b4
Browse files Browse the repository at this point in the history
  • Loading branch information
bmcfee committed Aug 17, 2016
2 parents 98469b4 + a3fbbf8 commit baa9bcb
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 100 deletions.
96 changes: 64 additions & 32 deletions docs/cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,61 +37,93 @@ The default configuration can be overridden by setting the following environment
- `LIBROSA_CACHE_MMAP` : optional memory mapping mode `{None, 'r+', 'r', 'w+', 'c'}`
- `LIBROSA_CACHE_COMPRESS` : flag to enable compression of data on disk `{0, 1}`
- `LIBROSA_CACHE_VERBOSE` : controls how much debug info is displayed. `{int, non-negative}`
- `LIBROSA_CACHE_LEVEL` : controls the caching level: the larger this value, the more data is cached. `{int}`

Please refer to the `joblib.Memory` `documentation
<https://pythonhosted.org/joblib/memory.html#memory-reference>`_ for a detailed explanation of these
parameters.


Cache levels
------------

Cache levels operate in a fashion similar to logging levels.
For small values of `LIBROSA_CACHE_LEVEL`, only the most important (frequently used) data are cached.
As the cache level increases, broader classes of functions are cached.
As a result, application code may run faster at the expense of larger disk usage.

The caching levels are described as follows:

- 10: filter bases, independent of audio data (dct, mel, chroma, constant-q)
- 20: low-level features (cqt, stft, zero-crossings, etc)
- 30: high-level features (tempo, beats, decomposition, recurrence, etc)
- 40: post-processing (delta, stack_memory, normalize, sync)

The default cache level is 10.


Example
-------
To demonstrate how to use the cache, we'll first call an example script twice without caching::

[~/git/librosa/examples]$ time ./estimate_tuning.py ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
Loading ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
$ time -p ./estimate_tuning.py ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Loading ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Separating harmonic component ...
Estimating tuning ...
+6.00 cents
real 0m4.369s
user 0m4.065s
sys 0m0.350s
[~/git/librosa/examples]$ time ./estimate_tuning.py ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
Loading ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
+9.00 cents
real 6.74
user 6.03
sys 1.09

$ time -p ./estimate_tuning.py ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Loading ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Separating harmonic component ...
Estimating tuning ...
+6.00 cents
real 0m4.414s
user 0m4.013s
sys 0m0.440s
+9.00 cents
real 6.68
user 6.04
sys 1.05


Next, we'll enable caching to `/tmp/librosa`::

[~/git/librosa/examples]$ export LIBROSA_CACHE_DIR=/tmp/librosa
$ export LIBROSA_CACHE_DIR=/tmp/librosa

and set the cache level to 50::

$ export LIBROSA_CACHE_LEVEL=50

And now we'll re-run the example script twice. The first time, there will be no cached values, so the time
should be similar to running without cache. The second time, we'll be able to reuse intermediate values, so
it should be significantly faster.::

[~/git/librosa/examples]$ time ./estimate_tuning.py ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
Loading ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
$ time -p ./estimate_tuning.py ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Loading ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Separating harmonic component ...
Estimating tuning ...
+6.00 cents
real 0m4.859s
user 0m4.471s
sys 0m0.429s
[~/git/librosa/examples]$ time ./estimate_tuning.py ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
Loading ../librosa/example_data/Kevin_MacLeod_-_Vibe_Ace.mp3
+9.00 cents
real 7.60
user 6.79
sys 1.15

$ time -p ./estimate_tuning.py ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Loading ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Separating harmonic component ...
Estimating tuning ...
+6.00 cents
real 0m0.931s
user 0m0.862s
sys 0m0.112s
+9.00 cents
real 1.64
user 1.30
sys 0.74

Reducing the cache level to 20 yields an intermediate acceleration::

$ export LIBROSA_CACHE_LEVEL=50

$ time -p ./estimate_tuning.py ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Loading ../librosa/util/example_data/Kevin_MacLeod_-_Vibe_Ace.ogg
Separating harmonic component ...
Estimating tuning ...
+9.00 cents
real 4.98
user 4.17
sys 1.22
4 changes: 2 additions & 2 deletions librosa/beat.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def beat_track(y=None, sr=22050, onset_envelope=None, hop_length=512,
return (bpm, beats)


@cache
@cache(level=30)
def estimate_tempo(onset_envelope, sr=22050, hop_length=512, start_bpm=120,
std_bpm=1.0, ac_size=4.0, duration=90.0, offset=0.0):
"""Estimate the tempo (beats per minute) from an onset envelope
Expand Down Expand Up @@ -297,7 +297,7 @@ def estimate_tempo(onset_envelope, sr=22050, hop_length=512, start_bpm=120,
return start_bpm


@cache
@cache(level=30)
def __beat_tracker(onset_envelope, bpm, fft_res, tightness, trim):
"""Internal function that tracks beats in an onset strength envelope.
Expand Down
52 changes: 34 additions & 18 deletions librosa/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,51 @@ class CacheManager(Memory):
field, thereby allowing librosa.cache to act as a decorator function.
'''

def __call__(self, function):
'''Decorator function. Adds an input/output cache to
the specified function.'''
def __init__(self, cachedir, level=10, **kwargs):
super(CacheManager, self).__init__(cachedir, **kwargs)
# The level parameter controls which data we cache
# smaller numbers mean less caching
self.level = level

from decorator import FunctionMaker
def __call__(self, level):
'''Example usage:
def decorator_apply(dec, func):
"""Decorate a function by preserving the signature even if dec
is not a signature-preserving decorator.
@cache(level=2)
def semi_important_function(some_arguments):
...
'''
def wrapper(function):
'''Decorator function. Adds an input/output cache to
the specified function.'''

This recipe is derived from
http://micheles.googlecode.com/hg/decorator/documentation.html#id14
"""
from decorator import FunctionMaker

return FunctionMaker.create(
func, 'return decorated(%(signature)s)',
dict(decorated=dec(func)), __wrapped__=func)
def decorator_apply(dec, func):
"""Decorate a function by preserving the signature even if dec
is not a signature-preserving decorator.
if self.cachedir is not None:
return decorator_apply(self.cache, function)
This recipe is derived from
http://micheles.googlecode.com/hg/decorator/documentation.html#id14
"""

return FunctionMaker.create(
func, 'return decorated(%(signature)s)',
dict(decorated=dec(func)), __wrapped__=func)

if self.cachedir is not None and self.level >= level:
return decorator_apply(self.cache, function)

else:
return function
return wrapper

else:
return function

# Instantiate the cache from the environment
CACHE = CacheManager(os.environ.get('LIBROSA_CACHE_DIR', None),
mmap_mode=os.environ.get('LIBROSA_CACHE_MMAP', None),
compress=os.environ.get('LIBROSA_CACHE_COMPRESS', False),
verbose=int(os.environ.get('LIBROSA_CACHE_VERBOSE', 0)))
verbose=int(os.environ.get('LIBROSA_CACHE_VERBOSE', 0)),
level=int(os.environ.get('LIBROSA_CACHE_LEVEL', 10)))

# Override the module's __call__ attribute
sys.modules[__name__] = CACHE
9 changes: 4 additions & 5 deletions librosa/core/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def load(path, sr=22050, mono=True, offset=0.0, duration=None,
return (y, sr)


@cache
@cache(level=20)
def to_mono(y):
'''Force an audio signal down to mono.
Expand Down Expand Up @@ -186,7 +186,7 @@ def to_mono(y):
return y


@cache
@cache(level=20)
def resample(y, orig_sr, target_sr, res_type='kaiser_best', fix=True, scale=False, **kwargs):
"""Resample a time series from orig_sr to target_sr
Expand Down Expand Up @@ -355,7 +355,7 @@ def get_duration(y=None, sr=22050, S=None, n_fft=2048, hop_length=512,
return float(n_samples) / sr


@cache
@cache(level=20)
def autocorrelate(y, max_size=None, axis=-1):
"""Bounded auto-correlation
Expand Down Expand Up @@ -422,7 +422,7 @@ def autocorrelate(y, max_size=None, axis=-1):
return autocorr


@cache
@cache(level=20)
def zero_crossings(y, threshold=1e-10, ref_magnitude=None, pad=True,
zero_pos=True, axis=-1):
'''Find the zero-crossings of a signal `y`: indices `i` such that
Expand Down Expand Up @@ -543,7 +543,6 @@ def zero_crossings(y, threshold=1e-10, ref_magnitude=None, pad=True,
constant_values=pad)


@cache
def clicks(times=None, frames=None, sr=22050, hop_length=512,
click_freq=1000.0, click_duration=0.1, click=None, length=None):
"""Returns a signal with the signal `click` placed at each specified time
Expand Down
7 changes: 4 additions & 3 deletions librosa/core/constantq.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
__all__ = ['cqt', 'hybrid_cqt', 'pseudo_cqt']


@cache
@cache(level=20)
def cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
bins_per_octave=12, tuning=None, filter_scale=1,
norm=1, sparsity=0.01, real=util.Deprecated()):
Expand Down Expand Up @@ -232,7 +232,7 @@ def cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
return __trim_stack(cqt_resp, n_bins)


@cache
@cache(level=20)
def hybrid_cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
bins_per_octave=12, tuning=None, filter_scale=1,
norm=1, sparsity=0.01):
Expand Down Expand Up @@ -349,7 +349,7 @@ def hybrid_cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
return __trim_stack(cqt_resp, n_bins)


@cache
@cache(level=20)
def pseudo_cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
bins_per_octave=12, tuning=None, filter_scale=1,
norm=1, sparsity=0.01):
Expand Down Expand Up @@ -432,6 +432,7 @@ def pseudo_cqt(y, sr=22050, hop_length=512, fmin=None, n_bins=84,
return fft_basis.dot(D)


@cache(level=10)
def __cqt_filter_fft(sr, fmin, n_bins, bins_per_octave, tuning,
filter_scale, norm, sparsity, hop_length=None):
'''Generate the frequency domain constant-Q filter basis.'''
Expand Down
2 changes: 1 addition & 1 deletion librosa/core/pitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def pitch_tuning(frequencies, resolution=0.01, bins_per_octave=12):
return tuning[np.argmax(counts)]


@cache
@cache(level=30)
def piptrack(y=None, sr=22050, S=None, n_fft=2048, hop_length=None,
fmin=150.0, fmax=4000.0, threshold=0.1):
'''Pitch tracking on thresholded parabolically-interpolated STFT
Expand Down
12 changes: 5 additions & 7 deletions librosa/core/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
'fmt']


@cache
@cache(level=20)
def stft(y, n_fft=2048, hop_length=None, win_length=None, window=None,
center=True, dtype=np.complex64):
"""Short-time Fourier transform (STFT)
Expand Down Expand Up @@ -186,7 +186,7 @@ def stft(y, n_fft=2048, hop_length=None, win_length=None, window=None,
return stft_matrix


@cache
@cache(level=20)
def istft(stft_matrix, hop_length=None, win_length=None, window=None,
center=True, dtype=np.float32):
"""
Expand Down Expand Up @@ -510,7 +510,6 @@ def magphase(D):
return mag, phase


@cache
def phase_vocoder(D, rate, hop_length=None):
"""Phase vocoder. Given an STFT matrix D, speed up by a factor of `rate`
Expand Down Expand Up @@ -598,7 +597,7 @@ def phase_vocoder(D, rate, hop_length=None):
return d_stretch


@cache
@cache(level=30)
def logamplitude(S, ref_power=1.0, amin=1e-10, top_db=80.0):
"""Log-scale the amplitude of a spectrogram.
Expand Down Expand Up @@ -702,7 +701,7 @@ def logamplitude(S, ref_power=1.0, amin=1e-10, top_db=80.0):
return log_spec


@cache
@cache(level=30)
def perceptual_weighting(S, frequencies, **kwargs):
'''Perceptual weighting of a power spectrogram:
Expand Down Expand Up @@ -770,7 +769,7 @@ def perceptual_weighting(S, frequencies, **kwargs):
return offset + logamplitude(S, **kwargs)


@cache
@cache(level=30)
def fmt(y, t_min=0.5, n_fmt=None, kind='cubic', beta=0.5, over_sample=1, axis=-1):
"""The fast Mellin transform (FMT) [1]_ of a uniformly sampled signal y.
Expand Down Expand Up @@ -970,7 +969,6 @@ def fmt(y, t_min=0.5, n_fmt=None, kind='cubic', beta=0.5, over_sample=1, axis=-1
return result[idx] * np.sqrt(n) / n_fmt


@cache
def _spectrogram(y=None, S=None, n_fft=2048, hop_length=512, power=1):
'''Helper function to retrieve a magnitude spectrogram.
Expand Down
4 changes: 2 additions & 2 deletions librosa/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def decompose(S, n_components=None, transformer=None, sort=False, fit=True, **kw
return components, activations


@cache
@cache(level=30)
def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0):
"""Median-filtering harmonic percussive source separation (HPSS).
Expand Down Expand Up @@ -371,7 +371,7 @@ def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0):
return ((S * mask_harm) * phase, (S * mask_perc) * phase)


@cache
@cache(level=30)
def nn_filter(S, rec=None, aggregate=None, axis=-1, **kwargs):
'''Filtering by nearest-neighbors.
Expand Down
1 change: 0 additions & 1 deletion librosa/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ def __call__(self, x, pos=None):
r'M3$_x$', r'M3$_y$'])


@cache
def cmap(data, robust=True, cmap_seq='magma', cmap_bool='gray_r', cmap_div='coolwarm'):
'''Get a default colormap from the given data.
Expand Down

0 comments on commit baa9bcb

Please sign in to comment.