# -*- coding: utf-8 -*-
"""
Display
=======
.. autosummary::
:toctree: generated/
specshow
waveplot
time_ticks
cmap
"""
import numpy as np
import copy
import matplotlib as mpl
import matplotlib.image as img
import matplotlib.pyplot as plt
import warnings
from . import cache
from . import core
from . import util
from .util.exceptions import ParameterError
_HAS_SEABORN = False
try:
_matplotlibrc = copy.deepcopy(mpl.rcParams)
import seaborn as sns
_HAS_SEABORN = True
mpl.rcParams.update(**_matplotlibrc)
except ImportError:
pass
# This function wraps xticks or yticks: star-args is okay
[docs]def time_ticks(locs, *args, **kwargs): # pylint: disable=star-args
'''Plot time-formatted axis ticks.
Parameters
----------
locations : list or np.ndarray
Time-stamps for tick marks
n_ticks : int > 0 or None
Show this number of ticks (evenly spaced).
If none, all ticks are displayed.
Default: 5
axis : 'x' or 'y'
Which axis should the ticks be plotted on?
Default: 'x'
time_fmt : None or {'ms', 's', 'm', 'h'}
- 'ms': milliseconds (eg, 241ms)
- 's': seconds (eg, 1.43s)
- 'm': minutes (eg, 1:02)
- 'h': hours (eg, 1:02:03)
If none, formatted is automatically selected by the
range of the times data.
Default: None
fmt : str
.. warning:: This parameter name was in librosa 0.4.2
Use the `time_fmt` parameter instead.
The `fmt` parameter will be removed in librosa 0.5.0.
kwargs : additional keyword arguments.
See `matplotlib.pyplot.xticks` or `yticks` for details.
Returns
-------
locs
labels
Locations and labels of tick marks
See Also
--------
matplotlib.pyplot.xticks
matplotlib.pyplot.yticks
Examples
--------
>>> # Tick at pre-computed beat times
>>> librosa.display.specshow(S)
>>> librosa.display.time_ticks(beat_times)
>>> # Set the locations of the time stamps
>>> librosa.display.time_ticks(locations, timestamps)
>>> # Format in seconds
>>> librosa.display.time_ticks(beat_times, time_fmt='s')
>>> # Tick along the y axis
>>> librosa.display.time_ticks(beat_times, axis='y')
'''
n_ticks = kwargs.pop('n_ticks', 5)
axis = kwargs.pop('axis', 'x')
fmt = kwargs.pop('fmt', util.Deprecated())
time_fmt = kwargs.pop('time_fmt', None)
time_fmt = util.rename_kw('fmt', fmt, 'time_fmt', time_fmt, '0.4.2', '0.5.0')
if axis == 'x':
ticker = plt.xticks
elif axis == 'y':
ticker = plt.yticks
else:
raise ParameterError("axis must be either 'x' or 'y'.")
if len(args) > 0:
times = args[0]
else:
times = locs
locs = np.arange(len(times))
if n_ticks is not None:
# Slice the locations and labels evenly between 0 and the last point
positions = np.linspace(0, len(locs)-1, n_ticks,
endpoint=True).astype(int)
locs = locs[positions]
times = times[positions]
# Format the labels by time
formats = {'ms': lambda t: '{:d}ms'.format(int(1e3 * t)),
's': '{:0.2f}s'.format,
'm': lambda t: '{:d}:{:02d}'.format(int(t / 6e1),
int(np.mod(t, 6e1))),
'h': lambda t: '{:d}:{:02d}:{:02d}'.format(int(t / 3.6e3),
int(np.mod(t / 6e1,
6e1)),
int(np.mod(t, 6e1)))}
if time_fmt is None:
if max(times) > 3.6e3:
time_fmt = 'h'
elif max(times) > 6e1:
time_fmt = 'm'
elif max(times) > 1.0:
time_fmt = 's'
else:
time_fmt = 'ms'
elif time_fmt not in formats:
raise ParameterError('Invalid format: {:s}'.format(time_fmt))
times = [formats[time_fmt](t) for t in times]
return ticker(locs, times, **kwargs)
def frequency_ticks(locs, *args, **kwargs): # pylint: disable=star-args
'''Plot frequency-formatted axis ticks.
Parameters
----------
locations : list or np.ndarray
Frequency values for tick marks
n_ticks : int > 0 or None
Show this number of ticks (evenly spaced).
If none, all ticks are displayed.
Default: 5
axis : 'x' or 'y'
Which axis should the ticks be plotted on?
Default: 'x'
freq_fmt : None or {'mHz', 'Hz', 'kHz', 'MHz', 'GHz'}
- 'mHz': millihertz
- 'Hz': hertz
- 'kHz': kilohertz
- 'MHz': megahertz
- 'GHz': gigahertz
If none, formatted is automatically selected by the
range of the frequency data.
Default: None
kwargs : additional keyword arguments.
See `matplotlib.pyplot.xticks` or `yticks` for details.
Returns
-------
(locs, labels)
Locations and labels of tick marks
label
Axis label
See Also
--------
matplotlib.pyplot.xticks
matplotlib.pyplot.yticks
Examples
--------
>>> # Tick at pre-computed beat times
>>> librosa.display.specshow(S)
>>> librosa.display.frequency_ticks()
>>> # Set the locations of the time stamps
>>> librosa.display.frequency_ticks(locations, frequencies)
>>> # Format in hertz
>>> librosa.display.frequency_ticks(frequencies, freq_fmt='Hz')
>>> # Tick along the y axis
>>> librosa.display.frequency_ticks(frequencies, axis='y')
'''
n_ticks = kwargs.pop('n_ticks', 5)
axis = kwargs.pop('axis', 'x')
freq_fmt = kwargs.pop('freq_fmt', None)
if axis == 'x':
ticker = plt.xticks
elif axis == 'y':
ticker = plt.yticks
else:
raise ParameterError("axis must be either 'x' or 'y'.")
if len(args) > 0:
freqs = args[0]
else:
freqs = locs
locs = np.arange(len(freqs))
if n_ticks is not None:
# Slice the locations and labels evenly between 0 and the last point
positions = np.linspace(0, len(locs)-1, n_ticks,
endpoint=True).astype(int)
locs = locs[positions]
freqs = freqs[positions]
# Format the labels by time
formats = {'mHz': lambda f: '{:.5g}'.format(f * 1e3),
'Hz': '{:.5g}'.format,
'kHz': lambda f: '{:.5g}'.format(f * 1e-3),
'MHz': lambda f: '{:.5g}'.format(f * 1e-6),
'GHz': lambda f: '{:.5g}'.format(f * 1e-9)}
f_max = np.max(freqs)
if freq_fmt is None:
if f_max > 1e10:
freq_fmt = 'GHz'
elif f_max > 1e7:
freq_fmt = 'MHz'
elif f_max > 1e4:
freq_fmt = 'kHz'
elif f_max > 1e1:
freq_fmt = 'Hz'
else:
freq_fmt = 'mHz'
elif freq_fmt not in formats:
raise ParameterError('Invalid format: {:s}'.format(freq_fmt))
ticks = [formats[freq_fmt](f) for f in freqs]
return ticker(locs, ticks, **kwargs), freq_fmt
@cache
[docs]def cmap(data, use_sns=True, robust=True):
'''Get a default colormap from the given data.
If the data is boolean, use a black and white colormap.
If the data has both positive and negative values,
use a diverging colormap ('coolwarm').
Otherwise, use a sequential map: either cubehelix or 'OrRd'.
Parameters
----------
data : np.ndarray
Input data
use_sns : bool
If True, and `seaborn` is installed, use cubehelix maps for
sequential data
robust : bool
If True, discard the top and bottom 2% of data when calculating
range.
Returns
-------
cmap : matplotlib.colors.Colormap
- If `data` has dtype=boolean, `cmap` is 'gray_r'
- If `data` has only positive or only negative values,
`cmap` is 'OrRd' (`use_sns==False`) or cubehelix
- If `data` has both positive and negatives, `cmap` is 'coolwarm'
See Also
--------
matplotlib.pyplot.colormaps
seaborn.cubehelix_palette
'''
data = np.atleast_1d(data)
if data.dtype == 'bool':
return plt.get_cmap('gray_r')
data = data[np.isfinite(data)]
if robust:
min_p, max_p = 2, 98
else:
min_p, max_p = 0, 100
max_val = np.percentile(data, max_p)
min_val = np.percentile(data, min_p)
if min_val >= 0 or max_val <= 0:
if use_sns and _HAS_SEABORN:
return sns.cubehelix_palette(light=1.0, as_cmap=True)
else:
return plt.get_cmap('OrRd')
return plt.get_cmap('coolwarm')
def __envelope(x, hop):
'''Compute the max-envelope of x at a stride/frame length of h'''
return util.frame(x, hop_length=hop, frame_length=hop).max(axis=0)
[docs]def waveplot(y, sr=22050, max_points=5e4, x_axis='time', offset=0.0, max_sr=1000,
time_fmt=None, **kwargs):
'''Plot the amplitude envelope of a waveform.
If `y` is monophonic, a filled curve is drawn between `[-abs(y), abs(y)]`.
If `y` is stereo, the curve is drawn between `[-abs(y[1]), abs(y[0])]`,
so that the left and right channels are drawn above and below the axis,
respectively.
Long signals (`duration >= max_points`) are down-sampled to at
most `max_sr` before plotting.
Parameters
----------
y : np.ndarray [shape=(n,) or (2,n)]
audio time series (mono or stereo)
sr : number > 0 [scalar]
sampling rate of `y`
max_points : postive number or None
Maximum number of time-points to plot: if `max_points` exceeds
the duration of `y`, then `y` is downsampled.
If `None`, no downsampling is performed.
x_axis : str {'time', 'off', 'none'} or None
If 'time', the x-axis is given time tick-marks.
See also: `time_ticks`
offset : float
Horizontal offset (in time) to start the waveform plot
max_sr : number > 0 [scalar]
Maximum sampling rate for the visualization
time_fmt : None or str
Formatting for time axis. None (automatic) by default.
See `time_ticks`.
kwargs
Additional keyword arguments to `matplotlib.pyplot.fill_between`
Returns
-------
pc : matplotlib.collections.PolyCollection
The PolyCollection created by `fill_between`.
See also
--------
time_ticks
librosa.core.resample
matplotlib.pyplot.fill_between
Examples
--------
Plot a monophonic waveform
>>> import matplotlib.pyplot as plt
>>> y, sr = librosa.load(librosa.util.example_audio_file(), duration=10)
>>> plt.figure()
>>> plt.subplot(3, 1, 1)
>>> librosa.display.waveplot(y, sr=sr)
>>> plt.title('Monophonic')
Or a stereo waveform
>>> y, sr = librosa.load(librosa.util.example_audio_file(),
... mono=False, duration=10)
>>> plt.subplot(3, 1, 2)
>>> librosa.display.waveplot(y, sr=sr)
>>> plt.title('Stereo')
Or harmonic and percussive components with transparency
>>> y, sr = librosa.load(librosa.util.example_audio_file(), duration=10)
>>> y_harm, y_perc = librosa.effects.hpss(y)
>>> plt.subplot(3, 1, 3)
>>> librosa.display.waveplot(y_harm, sr=sr, alpha=0.25)
>>> librosa.display.waveplot(y_perc, sr=sr, color='r', alpha=0.5)
>>> plt.title('Harmonic + Percussive')
>>> plt.tight_layout()
'''
util.valid_audio(y, mono=False)
if not (isinstance(max_sr, int) and max_sr > 0):
raise ParameterError('max_sr must be a non-negative integer')
target_sr = sr
if max_points is not None:
if max_points <= 0:
raise ParameterError('max_points must be strictly positive')
if max_points < y.shape[-1]:
target_sr = min(max_sr, (sr * y.shape[-1]) // max_points)
hop_length = sr // target_sr
if y.ndim == 1:
y = __envelope(y, hop_length)
else:
y = np.vstack([__envelope(_, hop_length) for _ in y])
if y.ndim > 1:
y_top = y[0]
y_bottom = -y[1]
else:
y_top = y
y_bottom = -y
axes = plt.gca()
if hasattr(axes._get_lines, 'prop_cycler'):
# matplotlib >= 1.5
kwargs.setdefault('color', next(axes._get_lines.prop_cycler)['color'])
else:
# matplotlib 1.4
kwargs.setdefault('color', next(axes._get_lines.color_cycle))
sample_off = core.time_to_samples(offset, sr=target_sr)
locs = np.arange(sample_off, sample_off + len(y_top))
out = axes.fill_between(locs, y_bottom, y_top, **kwargs)
plt.xlim([locs[0], locs[-1]])
if x_axis == 'time':
time_ticks(locs, core.samples_to_time(locs, sr=target_sr), time_fmt=time_fmt)
elif x_axis is None or x_axis in ['off', 'none']:
plt.xticks([])
else:
raise ParameterError('Unknown x_axis value: {}'.format(x_axis))
return out
[docs]def specshow(data, sr=22050, hop_length=512, x_axis=None, y_axis=None,
n_xticks=5, n_yticks=5, fmin=None, fmax=None, bins_per_octave=12,
tmin=16, tmax=240, freq_fmt='Hz', time_fmt=None, **kwargs):
'''Display a spectrogram/chromagram/cqt/etc.
Functions as a drop-in replacement for `matplotlib.pyplot.imshow`,
but with useful defaults.
Parameters
----------
data : np.ndarray [shape=(d, n)]
Matrix to display (e.g., spectrogram)
sr : number > 0 [scalar]
Sample rate used to determine time scale in x-axis.
hop_length : int > 0 [scalar]
Hop length, also used to determine time scale in x-axis
x_axis : None or str
y_axis : None or str
Range for the x- and y-axes.
Valid types are:
- None or 'off' : no axis is displayed.
Frequency types:
- 'linear' : frequency range is determined by the FFT window
and sampling rate.
- 'log' : the image is displayed on a vertical log scale.
- 'mel' : frequencies are determined by the mel scale.
- 'cqt_hz' : frequencies are determined by the CQT scale.
- 'cqt_note' : pitches are determined by the CQT scale.
- 'chroma' : pitches are determined by the chroma filters.
- 'tonnetz' : axes are labeled by Tonnetz dimensions
Time types:
- 'time' : markers are shown as milliseconds, seconds,
minutes, or hours
- 'lag' : like time, but past the half-way point counts
as negative values.
- 'frames' : markers are shown as frame counts.
- 'tempo' : markers are shown as beats-per-minute
n_xticks : int > 0 [scalar]
If x_axis is drawn, the number of ticks to show
n_yticks : int > 0 [scalar]
If y_axis is drawn, the number of ticks to show
fmin : float > 0 [scalar] or None
Frequency of the lowest spectrogram bin. Used for Mel and CQT
scales.
If `y_axis` is `cqt_hz` or `cqt_note` and `fmin` is not given,
it is set by default to `note_to_hz('C1')`.
fmax : float > 0 [scalar] or None
Used for setting the Mel frequency scales
bins_per_octave : int > 0 [scalar]
Number of bins per octave. Used for CQT frequency scale.
tmin : float > 0 [scalar]
tmax : float > 0 [scalar]
Minimum and maximum tempi displayed when `_axis='tempo'`,
as measured in beats per minute.
freq_fmt : None or str
Formatting for frequency axes. 'Hz', by default.
See `frequency_ticks`.
time_fmt : None or str
Formatting for time axes. None (automatic) by default.
See `time_ticks`.
kwargs : additional keyword arguments
Arguments passed through to `matplotlib.pyplot.imshow`.
Returns
-------
image : `matplotlib.image.AxesImage`
As returned from `matplotlib.pyplot.imshow`.
See Also
--------
cmap : Automatic colormap detection
time_ticks : time-formatted tick marks
frequency_ticks : frequency-formatted tick marks
matplotlib.pyplot.imshow
Examples
--------
Visualize an STFT power spectrum
>>> import matplotlib.pyplot as plt
>>> y, sr = librosa.load(librosa.util.example_audio_file())
>>> plt.figure(figsize=(12, 8))
>>> D = librosa.logamplitude(np.abs(librosa.stft(y))**2, ref_power=np.max)
>>> plt.subplot(4, 2, 1)
>>> librosa.display.specshow(D, y_axis='linear')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Linear-frequency power spectrogram')
Or on a logarithmic scale
>>> plt.subplot(4, 2, 2)
>>> librosa.display.specshow(D, y_axis='log')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Log-frequency power spectrogram')
Or use a CQT scale
>>> CQT = librosa.logamplitude(librosa.cqt(y, sr=sr)**2, ref_power=np.max)
>>> plt.subplot(4, 2, 3)
>>> librosa.display.specshow(CQT, y_axis='cqt_note')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Constant-Q power spectrogram (note)')
>>> plt.subplot(4, 2, 4)
>>> librosa.display.specshow(CQT, y_axis='cqt_hz')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Constant-Q power spectrogram (Hz)')
Draw a chromagram with pitch classes
>>> C = librosa.feature.chroma_cqt(y=y, sr=sr)
>>> plt.subplot(4, 2, 5)
>>> librosa.display.specshow(C, y_axis='chroma')
>>> plt.colorbar()
>>> plt.title('Chromagram')
Force a grayscale colormap (white -> black)
>>> plt.subplot(4, 2, 6)
>>> librosa.display.specshow(D, cmap='gray_r', y_axis='linear')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Linear power spectrogram (grayscale)')
Draw time markers automatically
>>> plt.subplot(4, 2, 7)
>>> librosa.display.specshow(D, x_axis='time', y_axis='log')
>>> plt.colorbar(format='%+2.0f dB')
>>> plt.title('Log power spectrogram')
Draw a tempogram with BPM markers
>>> plt.subplot(4, 2, 8)
>>> oenv = librosa.onset.onset_strength(y=y, sr=sr)
>>> tempo = librosa.beat.estimate_tempo(oenv, sr=sr)
>>> Tgram = librosa.feature.tempogram(y=y, sr=sr)
>>> librosa.display.specshow(Tgram[:100], x_axis='time', y_axis='tempo',
... tmin=tempo/4, tmax=tempo*2, n_yticks=4)
>>> plt.colorbar()
>>> plt.title('Tempogram')
>>> plt.tight_layout()
'''
kwargs.setdefault('aspect', 'auto')
kwargs.setdefault('origin', 'lower')
kwargs.setdefault('interpolation', 'nearest')
if np.issubdtype(data.dtype, np.complex):
warnings.warn('Trying to display complex-valued input. '
'Showing magnitude instead.')
data = np.abs(data)
kwargs.setdefault('cmap', cmap(data))
axes = plt.imshow(data, **kwargs)
all_params = dict(kwargs=kwargs,
sr=sr,
fmin=fmin,
fmax=fmax,
bins_per_octave=bins_per_octave,
tmin=tmin,
tmax=tmax,
hop_length=hop_length,
time_fmt=time_fmt,
freq_fmt=freq_fmt)
# Scale and decorate the axes
__axis(data, n_xticks, x_axis, horiz=True, minor=y_axis, **all_params)
__axis(data, n_yticks, y_axis, horiz=False, minor=x_axis, **all_params)
return axes
def __get_shape_artists(data, horiz):
'''Return size, ticker, and labeler'''
if horiz:
return data.shape[1], plt.xticks, plt.xlabel
else:
return data.shape[0], plt.yticks, plt.ylabel
def __axis(data, n_ticks, ax_type, horiz=False, **kwargs):
'''Dispatch function to decorate axes'''
axis_map = {'linear': __axis_linear,
'log': __axis_log,
'mel': __axis_mel,
'cqt_hz': __axis_cqt_hz,
'cqt_note': __axis_cqt_note,
'chroma': __axis_chroma,
'tonnetz': __axis_tonnetz,
'off': __axis_none,
'time': __axis_time,
'tempo': __axis_tempo,
'lag': __axis_lag,
'frames': __axis_frames}
if ax_type is None:
ax_type = 'off'
if ax_type not in axis_map:
raise ParameterError('Unknown axis type: {:s}'.format(ax_type))
func = axis_map[ax_type]
func(data, n_ticks, horiz=horiz, **kwargs)
def __axis_none(data, n_ticks, horiz, **_kwargs):
'''Empty axis artist'''
_, ticker, labeler = __get_shape_artists(data, horiz)
ticker([])
labeler('')
def __axis_log(data, n_ticks, horiz, sr=22050, kwargs=None,
secondary_axis='linear', minor=None, **_kwargs):
'''Plot a log-scaled image'''
axes_phantom = plt.gca()
if kwargs is None:
kwargs = dict()
aspect = kwargs.pop('aspect', None)
fmt = _kwargs.pop('freq_fmt', 'Hz')
n, ticker, labeler = __get_shape_artists(data, horiz)
t_log, t_inv = __log_scale(n)
if horiz:
axis = 'x'
if minor == 'log':
ax2 = __log_scale(data.shape[0])[0]
else:
ax2 = np.linspace(0, data.shape[0], data.shape[0]).astype(int)
ax1 = t_log
else:
axis = 'y'
if minor == 'log':
ax1 = __log_scale(data.shape[1])[0]
else:
ax1 = np.linspace(0, data.shape[1], data.shape[1]).astype(int)
ax2 = t_log
args = (ax1, ax2, data)
# NOTE: 2013-11-14 16:15:33 by Brian McFee <brm2132@columbia.edu>
# We draw the image twice here. This is a hack to get around
# NonUniformImage not properly setting hooks for color.
# Drawing twice enables things like colorbar() to work properly.
im_phantom = img.NonUniformImage(axes_phantom,
extent=(args[0].min(), args[0].max(),
args[1].min(), args[1].max()),
**kwargs)
im_phantom.set_data(*args)
kwargs['aspect'] = aspect
axes_phantom.images[0] = im_phantom
positions = np.linspace(0, n-1, n_ticks, endpoint=True).astype(int)
# One extra value here to catch nyquist
values = np.linspace(0, 0.5 * sr, n, endpoint=True)
_, label = frequency_ticks(positions, values[t_inv[positions]],
n_ticks=None, axis=axis, freq_fmt=fmt)
labeler(label)
def __axis_mel(data, n_ticks, horiz, fmin=None, fmax=None, **_kwargs):
'''Mel-scaled axes'''
fmt = _kwargs.pop('freq_fmt', 'Hz')
if horiz:
axis = 'x'
else:
axis = 'y'
n, ticker, labeler = __get_shape_artists(data, horiz)
positions = np.linspace(0, n-1, n_ticks).astype(int)
kwargs = {}
if fmin is not None:
kwargs['fmin'] = fmin
if fmax is not None:
kwargs['fmax'] = fmax
# only two star-args here, defined immediately above
# pylint: disable=star-args
values = core.mel_frequencies(n_mels=n+2, **kwargs)[positions]
_, label = frequency_ticks(positions, values,
n_ticks=None, axis=axis, freq_fmt=fmt)
labeler(label)
def __axis_chroma(data, n_ticks, horiz, bins_per_octave=12, **_kwargs):
'''Chroma axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
# Generate the template positions: C D E F G A B
pos = np.asarray([0, 2, 4, 5, 7, 9, 11]) * bins_per_octave // 12
n_octaves = np.ceil(n / float(bins_per_octave))
positions = pos.copy()
for i in range(1, int(n_octaves)):
positions = np.append(positions, pos + i * bins_per_octave, axis=0)
values = core.midi_to_note(positions * 12 // bins_per_octave, octave=False)
ticker(positions[:n], values[:n])
labeler('Pitch class')
def __axis_linear(data, n_ticks, horiz, sr=22050, **_kwargs):
'''Linear frequency axes'''
fmt = _kwargs.pop('freq_fmt', 'Hz')
if horiz:
axis = 'x'
else:
axis = 'y'
n, ticker, labeler = __get_shape_artists(data, horiz)
positions = np.linspace(0, n - 1, n_ticks, endpoint=True).astype(int)
values = (sr * np.linspace(0, 0.5, n_ticks, endpoint=True))
_, label = frequency_ticks(positions, values,
n_ticks=None, axis=axis, freq_fmt=fmt)
labeler(label)
def __axis_cqt(data, n_ticks, horiz, note=False, fmin=None,
bins_per_octave=12, **_kwargs):
'''CQT axes'''
if fmin is None:
fmin = core.note_to_hz('C1')
if horiz:
axis = 'x'
else:
axis = 'y'
fmt = _kwargs.pop('freq_fmt', 'Hz')
n, ticker, labeler = __get_shape_artists(data, horiz)
positions = np.linspace(0, n-1, num=n_ticks, endpoint=True).astype(int)
values = core.cqt_frequencies(n + 1,
fmin=fmin,
bins_per_octave=bins_per_octave)
if note:
values = core.hz_to_note(values[positions])
label = 'Note'
ticker(positions, values)
else:
values = values[positions]
_, label = frequency_ticks(positions, values,
n_ticks=None, axis=axis, freq_fmt=fmt)
labeler(label)
def __axis_cqt_hz(*args, **kwargs):
'''CQT in Hz'''
kwargs['note'] = False
__axis_cqt(*args, **kwargs)
def __axis_cqt_note(*args, **kwargs):
'''CQT in notes'''
kwargs['note'] = True
__axis_cqt(*args, **kwargs)
def __axis_time(data, n_ticks, horiz, sr=22050, hop_length=512, **_kwargs):
'''Time axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
if horiz:
axis = 'x'
else:
axis = 'y'
fmt = _kwargs.pop('time_fmt', None)
positions = np.linspace(0, n-1, n_ticks, endpoint=True).astype(int)
time_ticks(positions,
core.frames_to_time(positions, sr=sr, hop_length=hop_length),
n_ticks=None, time_fmt=fmt, axis=axis)
labeler('Time')
def __axis_tempo(data, n_ticks, horiz, sr=22050, hop_length=512, tmin=16, tmax=240, **_kwargs):
'''Tempo axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
nmin = min(n-1, sr * 60.0 / (hop_length * tmin))
nmax = max(1, sr * 60.0 / (hop_length * tmax))
positions = np.logspace(np.log2(nmin), np.log2(nmax),
num=n_ticks, endpoint=True, base=2).astype(int)
tempi = ['{:.1f}'.format(60 * float(sr) / (hop_length * t)) for t in positions]
ticker(positions, tempi)
labeler('Tempo (BPM)')
def __axis_lag(data, n_ticks, horiz, sr=22050, hop_length=512, **_kwargs):
'''Lag axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
if horiz:
axis = 'x'
else:
axis = 'y'
positions = np.linspace(0, n-1, n_ticks, endpoint=True).astype(int)
times = core.frames_to_time(positions, sr=sr, hop_length=hop_length)
times[positions >= n//2] -= times[-1]
time_ticks(positions, times, n_ticks=None, axis=axis)
labeler('Lag')
def __axis_tonnetz(data, n_ticks, horiz, **_kwargs):
'''Chroma axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
positions = np.arange(6)
values = [r'5$_x$', r'5$_y$',
r'm3$_x$', r'm3$_y$',
r'M3$_x$', r'M3$_y$']
ticker(positions, values)
labeler('Tonnetz')
def __axis_frames(data, n_ticks, horiz, label='Frames', **_kwargs):
'''Frame axes'''
n, ticker, labeler = __get_shape_artists(data, horiz)
positions = np.linspace(0, n-1, n_ticks, endpoint=True).astype(int)
ticker(positions, positions)
labeler(label)
def __log_scale(n):
'''Return a log-scale mapping of bins 0..n, and its inverse.
Parameters
----------
n : int > 0
Number of bins
Returns
-------
y : np.ndarray, shape=(n,)
y_inv : np.ndarray, shape=(n+1,)
'''
logn = np.log2(n)
y = n * (1 - np.logspace(-logn, 0, n, base=2, endpoint=True))[::-1]
y = y.astype(int)
y_inv = np.arange(len(y))
for i in range(len(y)-1):
y_inv[y[i]:y[i+1]] = i
return y, y_inv