|
|
|
|
|
""" |
|
Display |
|
======= |
|
|
|
Data visualization |
|
------------------ |
|
.. autosummary:: |
|
:toctree: generated/ |
|
|
|
specshow |
|
waveshow |
|
|
|
Axis formatting |
|
--------------- |
|
.. autosummary:: |
|
:toctree: generated/ |
|
|
|
TimeFormatter |
|
NoteFormatter |
|
SvaraFormatter |
|
LogHzFormatter |
|
ChromaFormatter |
|
ChromaSvaraFormatter |
|
TonnetzFormatter |
|
|
|
Miscellaneous |
|
------------- |
|
.. autosummary:: |
|
:toctree: generated/ |
|
|
|
cmap |
|
AdaptiveWaveplot |
|
|
|
""" |
|
|
|
import warnings |
|
|
|
import numpy as np |
|
from matplotlib.cm import get_cmap |
|
from matplotlib.axes import Axes |
|
from matplotlib.ticker import Formatter, ScalarFormatter |
|
from matplotlib.ticker import LogLocator, FixedLocator, MaxNLocator |
|
from matplotlib.ticker import SymmetricalLogLocator |
|
import matplotlib |
|
from packaging.version import parse as version_parse |
|
|
|
|
|
from . import core |
|
from . import util |
|
from .util.exceptions import ParameterError |
|
from .util.decorators import deprecate_positional_args |
|
|
|
__all__ = [ |
|
"specshow", |
|
"waveshow", |
|
"cmap", |
|
"TimeFormatter", |
|
"NoteFormatter", |
|
"LogHzFormatter", |
|
"ChromaFormatter", |
|
"TonnetzFormatter", |
|
"AdaptiveWaveplot", |
|
] |
|
|
|
|
|
class TimeFormatter(Formatter): |
|
"""A tick formatter for time axes. |
|
|
|
Automatically switches between seconds, minutes:seconds, |
|
or hours:minutes:seconds. |
|
|
|
Parameters |
|
---------- |
|
lag : bool |
|
If ``True``, then the time axis is interpreted in lag coordinates. |
|
Anything past the midpoint will be converted to negative time. |
|
|
|
unit : str or None |
|
Abbreviation of the physical unit for axis labels and ticks. |
|
Either equal to `s` (seconds) or `ms` (milliseconds) or None (default). |
|
If set to None, the resulting TimeFormatter object adapts its string |
|
representation to the duration of the underlying time range: |
|
`hh:mm:ss` above 3600 seconds; `mm:ss` between 60 and 3600 seconds; |
|
and `ss` below 60 seconds. |
|
|
|
|
|
See also |
|
-------- |
|
matplotlib.ticker.Formatter |
|
|
|
|
|
Examples |
|
-------- |
|
|
|
For normal time |
|
|
|
>>> import matplotlib.pyplot as plt |
|
>>> times = np.arange(30) |
|
>>> values = np.random.randn(len(times)) |
|
>>> fig, ax = plt.subplots() |
|
>>> ax.plot(times, values) |
|
>>> ax.xaxis.set_major_formatter(librosa.display.TimeFormatter()) |
|
>>> ax.set(xlabel='Time') |
|
|
|
Manually set the physical time unit of the x-axis to milliseconds |
|
|
|
>>> times = np.arange(100) |
|
>>> values = np.random.randn(len(times)) |
|
>>> fig, ax = plt.subplots() |
|
>>> ax.plot(times, values) |
|
>>> ax.xaxis.set_major_formatter(librosa.display.TimeFormatter(unit='ms')) |
|
>>> ax.set(xlabel='Time (ms)') |
|
|
|
For lag plots |
|
|
|
>>> times = np.arange(60) |
|
>>> values = np.random.randn(len(times)) |
|
>>> fig, ax = plt.subplots() |
|
>>> ax.plot(times, values) |
|
>>> ax.xaxis.set_major_formatter(librosa.display.TimeFormatter(lag=True)) |
|
>>> ax.set(xlabel='Lag') |
|
""" |
|
|
|
def __init__(self, lag=False, unit=None): |
|
|
|
if unit not in ["s", "ms", None]: |
|
raise ParameterError("Unknown time unit: {}".format(unit)) |
|
|
|
self.unit = unit |
|
self.lag = lag |
|
|
|
def __call__(self, x, pos=None): |
|
"""Return the time format as pos""" |
|
|
|
_, dmax = self.axis.get_data_interval() |
|
vmin, vmax = self.axis.get_view_interval() |
|
|
|
|
|
if self.lag and x >= dmax * 0.5: |
|
|
|
if x > dmax: |
|
return "" |
|
value = np.abs(x - dmax) |
|
|
|
sign = "-" |
|
else: |
|
value = x |
|
sign = "" |
|
|
|
if self.unit == "s": |
|
s = "{:.3g}".format(value) |
|
elif self.unit == "ms": |
|
s = "{:.3g}".format(value * 1000) |
|
else: |
|
if vmax - vmin > 3600: |
|
|
|
s = "{:d}:{:02d}:{:02d}".format( |
|
int(value / 3600.0), |
|
int(np.mod(value / 60.0, 60)), |
|
int(np.mod(value, 60)), |
|
) |
|
elif vmax - vmin > 60: |
|
|
|
s = "{:d}:{:02d}".format(int(value / 60.0), int(np.mod(value, 60))) |
|
elif vmax - vmin >= 1: |
|
|
|
s = "{:.2g}".format(value) |
|
else: |
|
|
|
s = "{:.3f}".format(value) |
|
|
|
return "{:s}{:s}".format(sign, s) |
|
|
|
|
|
class NoteFormatter(Formatter): |
|
"""Ticker formatter for Notes |
|
|
|
Parameters |
|
---------- |
|
octave : bool |
|
If ``True``, display the octave number along with the note name. |
|
|
|
Otherwise, only show the note name (and cent deviation) |
|
|
|
major : bool |
|
If ``True``, ticks are always labeled. |
|
|
|
If ``False``, ticks are only labeled if the span is less than 2 octaves |
|
|
|
key : str |
|
Key for determining pitch spelling. |
|
|
|
unicode : bool |
|
If ``True``, use unicode symbols for accidentals. |
|
|
|
If ``False``, use ASCII symbols for accidentals. |
|
|
|
See also |
|
-------- |
|
LogHzFormatter |
|
matplotlib.ticker.Formatter |
|
|
|
Examples |
|
-------- |
|
>>> import matplotlib.pyplot as plt |
|
>>> values = librosa.midi_to_hz(np.arange(48, 72)) |
|
>>> fig, ax = plt.subplots(nrows=2) |
|
>>> ax[0].bar(np.arange(len(values)), values) |
|
>>> ax[0].set(ylabel='Hz') |
|
>>> ax[1].bar(np.arange(len(values)), values) |
|
>>> ax[1].yaxis.set_major_formatter(librosa.display.NoteFormatter()) |
|
>>> ax[1].set(ylabel='Note') |
|
""" |
|
|
|
def __init__(self, octave=True, major=True, key="C:maj", unicode=True): |
|
|
|
self.octave = octave |
|
self.major = major |
|
self.key = key |
|
self.unicode = unicode |
|
|
|
def __call__(self, x, pos=None): |
|
|
|
if x <= 0: |
|
return "" |
|
|
|
|
|
vmin, vmax = self.axis.get_view_interval() |
|
|
|
if not self.major and vmax > 4 * max(1, vmin): |
|
return "" |
|
|
|
cents = vmax <= 2 * max(1, vmin) |
|
|
|
return core.hz_to_note( |
|
x, octave=self.octave, cents=cents, key=self.key, unicode=self.unicode |
|
) |
|
|
|
|
|
class SvaraFormatter(Formatter): |
|
"""Ticker formatter for Svara |
|
|
|
Parameters |
|
---------- |
|
octave : bool |
|
If ``True``, display the octave number along with the note name. |
|
|
|
Otherwise, only show the note name (and cent deviation) |
|
|
|
major : bool |
|
If ``True``, ticks are always labeled. |
|
|
|
If ``False``, ticks are only labeled if the span is less than 2 octaves |
|
|
|
Sa : number > 0 |
|
Frequency (in Hz) of Sa |
|
|
|
mela : str or int |
|
For Carnatic svara, the index or name of the melakarta raga in question |
|
|
|
To use Hindustani svara, set ``mela=None`` |
|
|
|
unicode : bool |
|
If ``True``, use unicode symbols for accidentals. |
|
|
|
If ``False``, use ASCII symbols for accidentals. |
|
|
|
See also |
|
-------- |
|
NoteFormatter |
|
matplotlib.ticker.Formatter |
|
librosa.hz_to_svara_c |
|
librosa.hz_to_svara_h |
|
|
|
|
|
Examples |
|
-------- |
|
>>> import matplotlib.pyplot as plt |
|
>>> values = librosa.midi_to_hz(np.arange(48, 72)) |
|
>>> fig, ax = plt.subplots(nrows=2) |
|
>>> ax[0].bar(np.arange(len(values)), values) |
|
>>> ax[0].set(ylabel='Hz') |
|
>>> ax[1].bar(np.arange(len(values)), values) |
|
>>> ax[1].yaxis.set_major_formatter(librosa.display.SvaraFormatter(261)) |
|
>>> ax[1].set(ylabel='Note') |
|
""" |
|
|
|
def __init__( |
|
self, Sa, octave=True, major=True, abbr=False, mela=None, unicode=True |
|
): |
|
|
|
if Sa is None: |
|
raise ParameterError( |
|
"Sa frequency is required for svara display formatting" |
|
) |
|
|
|
self.Sa = Sa |
|
self.octave = octave |
|
self.major = major |
|
self.abbr = abbr |
|
self.mela = mela |
|
self.unicode = unicode |
|
|
|
def __call__(self, x, pos=None): |
|
|
|
if x <= 0: |
|
return "" |
|
|
|
|
|
vmin, vmax = self.axis.get_view_interval() |
|
|
|
if not self.major and vmax > 4 * max(1, vmin): |
|
return "" |
|
|
|
if self.mela is None: |
|
return core.hz_to_svara_h( |
|
x, Sa=self.Sa, octave=self.octave, abbr=self.abbr, unicode=self.unicode |
|
) |
|
else: |
|
return core.hz_to_svara_c( |
|
x, |
|
Sa=self.Sa, |
|
mela=self.mela, |
|
octave=self.octave, |
|
abbr=self.abbr, |
|
unicode=self.unicode, |
|
) |
|
|
|
|
|
class LogHzFormatter(Formatter): |
|
"""Ticker formatter for logarithmic frequency |
|
|
|
Parameters |
|
---------- |
|
major : bool |
|
If ``True``, ticks are always labeled. |
|
|
|
If ``False``, ticks are only labeled if the span is less than 2 octaves |
|
|
|
See also |
|
-------- |
|
NoteFormatter |
|
matplotlib.ticker.Formatter |
|
|
|
Examples |
|
-------- |
|
>>> import matplotlib.pyplot as plt |
|
>>> values = librosa.midi_to_hz(np.arange(48, 72)) |
|
>>> fig, ax = plt.subplots(nrows=2) |
|
>>> ax[0].bar(np.arange(len(values)), values) |
|
>>> ax[0].yaxis.set_major_formatter(librosa.display.LogHzFormatter()) |
|
>>> ax[0].set(ylabel='Hz') |
|
>>> ax[1].bar(np.arange(len(values)), values) |
|
>>> ax[1].yaxis.set_major_formatter(librosa.display.NoteFormatter()) |
|
>>> ax[1].set(ylabel='Note') |
|
""" |
|
|
|
def __init__(self, major=True): |
|
|
|
self.major = major |
|
|
|
def __call__(self, x, pos=None): |
|
|
|
if x <= 0: |
|
return "" |
|
|
|
vmin, vmax = self.axis.get_view_interval() |
|
|
|
if not self.major and vmax > 4 * max(1, vmin): |
|
return "" |
|
|
|
return "{:g}".format(x) |
|
|
|
|
|
class ChromaFormatter(Formatter): |
|
"""A formatter for chroma axes |
|
|
|
See also |
|
-------- |
|
matplotlib.ticker.Formatter |
|
|
|
Examples |
|
-------- |
|
>>> import matplotlib.pyplot as plt |
|
>>> values = np.arange(12) |
|
>>> fig, ax = plt.subplots() |
|
>>> ax.plot(values) |
|
>>> ax.yaxis.set_major_formatter(librosa.display.ChromaFormatter()) |
|
>>> ax.set(ylabel='Pitch class') |
|
""" |
|
|
|
def __init__(self, key="C:maj", unicode=True): |
|
self.key = key |
|
self.unicode = unicode |
|
|
|
def __call__(self, x, pos=None): |
|
"""Format for chroma positions""" |
|
return core.midi_to_note( |
|
int(x), octave=False, cents=False, key=self.key, unicode=self.unicode |
|
) |
|
|
|
|
|
class ChromaSvaraFormatter(Formatter): |
|
"""A formatter for chroma axes with svara instead of notes. |
|
|
|
If mela is given, Carnatic svara names will be used. |
|
|
|
Otherwise, Hindustani svara names will be used. |
|
|
|
If `Sa` is not given, it will default to 0 (equivalent to `C`). |
|
|
|
See Also |
|
-------- |
|
ChromaFormatter |
|
|
|
""" |
|
|
|
def __init__(self, Sa=None, mela=None, abbr=True, unicode=True): |
|
if Sa is None: |
|
Sa = 0 |
|
self.Sa = Sa |
|
self.mela = mela |
|
self.abbr = abbr |
|
self.unicode = unicode |
|
|
|
def __call__(self, x, pos=None): |
|
"""Format for chroma positions""" |
|
if self.mela is not None: |
|
return core.midi_to_svara_c( |
|
int(x), |
|
Sa=self.Sa, |
|
mela=self.mela, |
|
octave=False, |
|
abbr=self.abbr, |
|
unicode=self.unicode, |
|
) |
|
else: |
|
return core.midi_to_svara_h( |
|
int(x), Sa=self.Sa, octave=False, abbr=self.abbr, unicode=self.unicode |
|
) |
|
|
|
|
|
class TonnetzFormatter(Formatter): |
|
"""A formatter for tonnetz axes |
|
|
|
See also |
|
-------- |
|
matplotlib.ticker.Formatter |
|
|
|
Examples |
|
-------- |
|
>>> import matplotlib.pyplot as plt |
|
>>> values = np.arange(6) |
|
>>> fig, ax = plt.subplots() |
|
>>> ax.plot(values) |
|
>>> ax.yaxis.set_major_formatter(librosa.display.TonnetzFormatter()) |
|
>>> ax.set(ylabel='Tonnetz') |
|
""" |
|
|
|
def __call__(self, x, pos=None): |
|
"""Format for tonnetz positions""" |
|
return [r"5$_x$", r"5$_y$", r"m3$_x$", r"m3$_y$", r"M3$_x$", r"M3$_y$"][int(x)] |
|
|
|
|
|
class AdaptiveWaveplot: |
|
"""A helper class for managing adaptive wave visualizations. |
|
|
|
This object is used to dynamically switch between sample-based and envelope-based |
|
visualizations of waveforms. |
|
When the display is zoomed in such that no more than `max_samples` would be |
|
visible, the sample-based display is used. |
|
When displaying the raw samples would require more than `max_samples`, an |
|
envelope-based plot is used instead. |
|
|
|
You should never need to instantiate this object directly, as it is constructed |
|
automatically by `waveshow`. |
|
|
|
Parameters |
|
---------- |
|
times : np.ndarray |
|
An array containing the time index (in seconds) for each sample. |
|
|
|
y : np.ndarray |
|
An array containing the (monophonic) wave samples. |
|
|
|
steps : matplotlib.lines.Lines2D |
|
The matplotlib artist used for the sample-based visualization. |
|
This is constructed by `matplotlib.pyplot.step`. |
|
|
|
envelope : matplotlib.collections.PolyCollection |
|
The matplotlib artist used for the envelope-based visualization. |
|
This is constructed by `matplotlib.pyplot.fill_between`. |
|
|
|
sr : number > 0 |
|
The sampling rate of the audio |
|
|
|
max_samples : int > 0 |
|
The maximum number of samples to use for sample-based display. |
|
|
|
See Also |
|
-------- |
|
waveshow |
|
""" |
|
|
|
def __init__(self, times, y, steps, envelope, sr=22050, max_samples=11025): |
|
self.times = times |
|
self.samples = y |
|
self.steps = steps |
|
self.envelope = envelope |
|
self.sr = sr |
|
self.max_samples = max_samples |
|
|
|
def update(self, ax): |
|
"""Update the matplotlib display according to the current viewport limits. |
|
|
|
This is a callback function, and should not be used directly. |
|
|
|
Parameters |
|
---------- |
|
ax : matplotlib axes object |
|
The axes object to update |
|
""" |
|
lims = ax.viewLim |
|
|
|
|
|
|
|
if lims.width * self.sr <= self.max_samples: |
|
self.envelope.set_visible(False) |
|
self.steps.set_visible(True) |
|
|
|
|
|
xdata = self.steps.get_xdata() |
|
if lims.x0 <= xdata[0] or lims.x1 >= xdata[-1]: |
|
|
|
|
|
midpoint_time = (lims.x1 + lims.x0) / 2 |
|
idx_start = np.searchsorted( |
|
self.times, midpoint_time - 0.5 * self.max_samples / self.sr |
|
) |
|
self.steps.set_data( |
|
self.times[idx_start : idx_start + self.max_samples], |
|
self.samples[idx_start : idx_start + self.max_samples], |
|
) |
|
else: |
|
|
|
self.envelope.set_visible(True) |
|
self.steps.set_visible(False) |
|
|
|
ax.figure.canvas.draw_idle() |
|
|
|
|
|
@deprecate_positional_args |
|
def cmap( |
|
data, *, robust=True, cmap_seq="magma", cmap_bool="gray_r", cmap_div="coolwarm" |
|
): |
|
"""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. |
|
|
|
Otherwise, use a sequential colormap. |
|
|
|
Parameters |
|
---------- |
|
data : np.ndarray |
|
Input data |
|
robust : bool |
|
If True, discard the top and bottom 2% of data when calculating |
|
range. |
|
cmap_seq : str |
|
The sequential colormap name |
|
cmap_bool : str |
|
The boolean colormap name |
|
cmap_div : str |
|
The diverging colormap name |
|
|
|
Returns |
|
------- |
|
cmap : matplotlib.colors.Colormap |
|
The colormap to use for ``data`` |
|
|
|
See Also |
|
-------- |
|
matplotlib.pyplot.colormaps |
|
""" |
|
|
|
data = np.atleast_1d(data) |
|
|
|
if data.dtype == "bool": |
|
return get_cmap(cmap_bool, lut=2) |
|
|
|
data = data[np.isfinite(data)] |
|
|
|
if robust: |
|
min_p, max_p = 2, 98 |
|
else: |
|
min_p, max_p = 0, 100 |
|
|
|
min_val, max_val = np.percentile(data, [min_p, max_p]) |
|
|
|
if min_val >= 0 or max_val <= 0: |
|
return get_cmap(cmap_seq) |
|
|
|
return get_cmap(cmap_div) |
|
|
|
|
|
def __envelope(x, hop): |
|
"""Compute the max-envelope of non-overlapping frames of x at length hop |
|
|
|
x is assumed to be multi-channel, of shape (n_channels, n_samples). |
|
""" |
|
x_frame = np.abs(util.frame(x, frame_length=hop, hop_length=hop)) |
|
return x_frame.max(axis=1) |
|
|
|
|
|
@deprecate_positional_args |
|
def specshow( |
|
data, |
|
*, |
|
x_coords=None, |
|
y_coords=None, |
|
x_axis=None, |
|
y_axis=None, |
|
sr=22050, |
|
hop_length=512, |
|
n_fft=None, |
|
win_length=None, |
|
fmin=None, |
|
fmax=None, |
|
tuning=0.0, |
|
bins_per_octave=12, |
|
key="C:maj", |
|
Sa=None, |
|
mela=None, |
|
thaat=None, |
|
auto_aspect=True, |
|
htk=False, |
|
unicode=True, |
|
ax=None, |
|
**kwargs, |
|
): |
|
"""Display a spectrogram/chromagram/cqt/etc. |
|
|
|
For a detailed overview of this function, see :ref:`sphx_glr_auto_examples_plot_display.py` |
|
|
|
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 |
|
|
|
n_fft : int > 0 or None |
|
Number of samples per frame in STFT/spectrogram displays. |
|
By default, this will be inferred from the shape of ``data`` |
|
as ``2 * (d - 1)``. |
|
If ``data`` was generated using an odd frame length, the correct |
|
value can be specified here. |
|
|
|
win_length : int > 0 or None |
|
The number of samples per window. |
|
By default, this will be inferred to match ``n_fft``. |
|
This is primarily useful for specifying odd window lengths in |
|
Fourier tempogram displays. |
|
|
|
x_axis, y_axis : None or str |
|
Range for the x- and y-axes. |
|
|
|
Valid types are: |
|
|
|
- None, 'none', or 'off' : no axis decoration is displayed. |
|
|
|
Frequency types: |
|
|
|
- 'linear', 'fft', 'hz' : frequency range is determined by |
|
the FFT window and sampling rate. |
|
- 'log' : the spectrum is displayed on a log scale. |
|
- 'fft_note': the spectrum is displayed on a log scale with pitches marked. |
|
- 'fft_svara': the spectrum is displayed on a log scale with svara marked. |
|
- '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. |
|
- 'cqt_svara' : like `cqt_note` but using Hindustani or Carnatic svara |
|
|
|
All frequency types are plotted in units of Hz. |
|
|
|
Any spectrogram parameters (hop_length, sr, bins_per_octave, etc.) |
|
used to generate the input data should also be provided when |
|
calling `specshow`. |
|
|
|
Categorical types: |
|
|
|
- 'chroma' : pitches are determined by the chroma filters. |
|
Pitch classes are arranged at integer locations (0-11) according to |
|
a given key. |
|
|
|
- `chroma_h`, `chroma_c`: pitches are determined by chroma filters, |
|
and labeled as svara in the Hindustani (`chroma_h`) or Carnatic (`chroma_c`) |
|
according to a given thaat (Hindustani) or melakarta raga (Carnatic). |
|
|
|
- 'tonnetz' : axes are labeled by Tonnetz dimensions (0-5) |
|
- 'frames' : markers are shown as frame counts. |
|
|
|
Time types: |
|
|
|
- 'time' : markers are shown as milliseconds, seconds, minutes, or hours. |
|
Values are plotted in units of seconds. |
|
- 's' : markers are shown as seconds. |
|
- 'ms' : markers are shown as milliseconds. |
|
- 'lag' : like time, but past the halfway point counts as negative values. |
|
- 'lag_s' : same as lag, but in seconds. |
|
- 'lag_ms' : same as lag, but in milliseconds. |
|
|
|
Rhythm: |
|
|
|
- 'tempo' : markers are shown as beats-per-minute (BPM) |
|
using a logarithmic scale. This is useful for |
|
visualizing the outputs of `feature.tempogram`. |
|
|
|
- 'fourier_tempo' : same as `'tempo'`, but used when |
|
tempograms are calculated in the Frequency domain |
|
using `feature.fourier_tempogram`. |
|
|
|
x_coords, y_coords : np.ndarray [shape=data.shape[0 or 1]] |
|
Optional positioning coordinates of the input data. |
|
These can be use to explicitly set the location of each |
|
element ``data[i, j]``, e.g., for displaying beat-synchronous |
|
features in natural time coordinates. |
|
|
|
If not provided, they are inferred from ``x_axis`` and ``y_axis``. |
|
|
|
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 |
|
|
|
tuning : float |
|
Tuning deviation from A440, in fractions of a bin. |
|
|
|
This is used for CQT frequency scales, so that ``fmin`` is adjusted |
|
to ``fmin * 2**(tuning / bins_per_octave)``. |
|
|
|
bins_per_octave : int > 0 [scalar] |
|
Number of bins per octave. Used for CQT frequency scale. |
|
|
|
key : str |
|
The reference key to use when using note axes (`cqt_note`, `chroma`). |
|
|
|
Sa : float or int |
|
If using Hindustani or Carnatic svara axis decorations, specify Sa. |
|
|
|
For `cqt_svara`, ``Sa`` should be specified as a frequency in Hz. |
|
|
|
For `chroma_c` or `chroma_h`, ``Sa`` should correspond to the position |
|
of Sa within the chromagram. |
|
If not provided, Sa will default to 0 (equivalent to `C`) |
|
|
|
mela : str or int, optional |
|
If using `chroma_c` or `cqt_svara` display mode, specify the melakarta raga. |
|
|
|
thaat : str, optional |
|
If using `chroma_h` display mode, specify the parent thaat. |
|
|
|
auto_aspect : bool |
|
Axes will have 'equal' aspect if the horizontal and vertical dimensions |
|
cover the same extent and their types match. |
|
|
|
To override, set to `False`. |
|
|
|
htk : bool |
|
If plotting on a mel frequency axis, specify which version of the mel |
|
scale to use. |
|
|
|
- `False`: use Slaney formula (default) |
|
- `True`: use HTK formula |
|
|
|
See `core.mel_frequencies` for more information. |
|
|
|
unicode : bool |
|
If using note or svara decorations, setting `unicode=True` |
|
will use unicode glyphs for accidentals and octave encoding. |
|
|
|
Setting `unicode=False` will use ASCII glyphs. This can be helpful |
|
if your font does not support musical notation symbols. |
|
|
|
ax : matplotlib.axes.Axes or None |
|
Axes to plot on instead of the default `plt.gca()`. |
|
|
|
**kwargs : additional keyword arguments |
|
Arguments passed through to `matplotlib.pyplot.pcolormesh`. |
|
|
|
By default, the following options are set: |
|
|
|
- ``rasterized=True`` |
|
- ``shading='auto'`` |
|
- ``edgecolors='None'`` |
|
|
|
Returns |
|
------- |
|
colormesh : `matplotlib.collections.QuadMesh` |
|
The color mesh object produced by `matplotlib.pyplot.pcolormesh` |
|
|
|
See Also |
|
-------- |
|
cmap : Automatic colormap detection |
|
matplotlib.pyplot.pcolormesh |
|
|
|
Examples |
|
-------- |
|
Visualize an STFT power spectrum using default parameters |
|
|
|
>>> import matplotlib.pyplot as plt |
|
>>> y, sr = librosa.load(librosa.ex('choice'), duration=15) |
|
>>> fig, ax = plt.subplots(nrows=2, ncols=1, sharex=True) |
|
>>> D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max) |
|
>>> img = librosa.display.specshow(D, y_axis='linear', x_axis='time', |
|
... sr=sr, ax=ax[0]) |
|
>>> ax[0].set(title='Linear-frequency power spectrogram') |
|
>>> ax[0].label_outer() |
|
|
|
Or on a logarithmic scale, and using a larger hop |
|
|
|
>>> hop_length = 1024 |
|
>>> D = librosa.amplitude_to_db(np.abs(librosa.stft(y, hop_length=hop_length)), |
|
... ref=np.max) |
|
>>> librosa.display.specshow(D, y_axis='log', sr=sr, hop_length=hop_length, |
|
... x_axis='time', ax=ax[1]) |
|
>>> ax[1].set(title='Log-frequency power spectrogram') |
|
>>> ax[1].label_outer() |
|
>>> fig.colorbar(img, ax=ax, format="%+2.f dB") |
|
""" |
|
|
|
if np.issubdtype(data.dtype, np.complexfloating): |
|
warnings.warn( |
|
"Trying to display complex-valued input. " "Showing magnitude instead.", |
|
stacklevel=2, |
|
) |
|
data = np.abs(data) |
|
|
|
kwargs.setdefault("cmap", cmap(data)) |
|
kwargs.setdefault("rasterized", True) |
|
kwargs.setdefault("edgecolors", "None") |
|
kwargs.setdefault("shading", "auto") |
|
|
|
all_params = dict( |
|
kwargs=kwargs, |
|
sr=sr, |
|
fmin=fmin, |
|
fmax=fmax, |
|
tuning=tuning, |
|
bins_per_octave=bins_per_octave, |
|
hop_length=hop_length, |
|
n_fft=n_fft, |
|
win_length=win_length, |
|
key=key, |
|
htk=htk, |
|
unicode=unicode, |
|
) |
|
|
|
|
|
y_coords = __mesh_coords(y_axis, y_coords, data.shape[0], **all_params) |
|
x_coords = __mesh_coords(x_axis, x_coords, data.shape[1], **all_params) |
|
|
|
axes = __check_axes(ax) |
|
|
|
out = axes.pcolormesh(x_coords, y_coords, data, **kwargs) |
|
|
|
__set_current_image(ax, out) |
|
|
|
|
|
__scale_axes(axes, x_axis, "x") |
|
__scale_axes(axes, y_axis, "y") |
|
|
|
|
|
__decorate_axis( |
|
axes.xaxis, x_axis, key=key, Sa=Sa, mela=mela, thaat=thaat, unicode=unicode |
|
) |
|
__decorate_axis( |
|
axes.yaxis, y_axis, key=key, Sa=Sa, mela=mela, thaat=thaat, unicode=unicode |
|
) |
|
|
|
|
|
if __same_axes(x_axis, y_axis, axes.get_xlim(), axes.get_ylim()) and auto_aspect: |
|
axes.set_aspect("equal") |
|
|
|
return out |
|
|
|
|
|
def __set_current_image(ax, img): |
|
"""Helper to set the current image in pyplot mode. |
|
|
|
If the provided ``ax`` is not `None`, then we assume that the user is using the object API. |
|
In this case, the pyplot current image is not set. |
|
""" |
|
|
|
if ax is None: |
|
import matplotlib.pyplot as plt |
|
|
|
plt.sci(img) |
|
|
|
|
|
def __mesh_coords(ax_type, coords, n, **kwargs): |
|
"""Compute axis coordinates""" |
|
|
|
if coords is not None: |
|
if len(coords) not in (n, n + 1): |
|
raise ParameterError( |
|
f"Coordinate shape mismatch: {len(coords)}!={n} or {n}+1" |
|
) |
|
return coords |
|
|
|
coord_map = { |
|
"linear": __coord_fft_hz, |
|
"fft": __coord_fft_hz, |
|
"fft_note": __coord_fft_hz, |
|
"fft_svara": __coord_fft_hz, |
|
"hz": __coord_fft_hz, |
|
"log": __coord_fft_hz, |
|
"mel": __coord_mel_hz, |
|
"cqt": __coord_cqt_hz, |
|
"cqt_hz": __coord_cqt_hz, |
|
"cqt_note": __coord_cqt_hz, |
|
"cqt_svara": __coord_cqt_hz, |
|
"chroma": __coord_chroma, |
|
"chroma_c": __coord_chroma, |
|
"chroma_h": __coord_chroma, |
|
"time": __coord_time, |
|
"s": __coord_time, |
|
"ms": __coord_time, |
|
"lag": __coord_time, |
|
"lag_s": __coord_time, |
|
"lag_ms": __coord_time, |
|
"tonnetz": __coord_n, |
|
"off": __coord_n, |
|
"tempo": __coord_tempo, |
|
"fourier_tempo": __coord_fourier_tempo, |
|
"frames": __coord_n, |
|
None: __coord_n, |
|
} |
|
|
|
if ax_type not in coord_map: |
|
raise ParameterError("Unknown axis type: {}".format(ax_type)) |
|
return coord_map[ax_type](n, **kwargs) |
|
|
|
|
|
def __check_axes(axes): |
|
"""Check if "axes" is an instance of an axis object. If not, use `gca`.""" |
|
if axes is None: |
|
import matplotlib.pyplot as plt |
|
|
|
axes = plt.gca() |
|
elif not isinstance(axes, Axes): |
|
raise ParameterError( |
|
"`axes` must be an instance of matplotlib.axes.Axes. " |
|
"Found type(axes)={}".format(type(axes)) |
|
) |
|
return axes |
|
|
|
|
|
def __scale_axes(axes, ax_type, which): |
|
"""Set the axis scaling""" |
|
|
|
kwargs = dict() |
|
if which == "x": |
|
if version_parse(matplotlib.__version__) < version_parse("3.3.0"): |
|
thresh = "linthreshx" |
|
base = "basex" |
|
scale = "linscalex" |
|
else: |
|
thresh = "linthresh" |
|
base = "base" |
|
scale = "linscale" |
|
|
|
scaler = axes.set_xscale |
|
limit = axes.set_xlim |
|
else: |
|
if version_parse(matplotlib.__version__) < version_parse("3.3.0"): |
|
thresh = "linthreshy" |
|
base = "basey" |
|
scale = "linscaley" |
|
else: |
|
thresh = "linthresh" |
|
base = "base" |
|
scale = "linscale" |
|
|
|
scaler = axes.set_yscale |
|
limit = axes.set_ylim |
|
|
|
|
|
if ax_type == "mel": |
|
mode = "symlog" |
|
kwargs[thresh] = 1000.0 |
|
kwargs[base] = 2 |
|
|
|
elif ax_type in ["cqt", "cqt_hz", "cqt_note", "cqt_svara"]: |
|
mode = "log" |
|
kwargs[base] = 2 |
|
|
|
elif ax_type in ["log", "fft_note", "fft_svara"]: |
|
mode = "symlog" |
|
kwargs[base] = 2 |
|
kwargs[thresh] = core.note_to_hz("C2") |
|
kwargs[scale] = 0.5 |
|
|
|
elif ax_type in ["tempo", "fourier_tempo"]: |
|
mode = "log" |
|
kwargs[base] = 2 |
|
limit(16, 480) |
|
else: |
|
return |
|
|
|
scaler(mode, **kwargs) |
|
|
|
|
|
def __decorate_axis( |
|
axis, ax_type, key="C:maj", Sa=None, mela=None, thaat=None, unicode=True |
|
): |
|
"""Configure axis tickers, locators, and labels""" |
|
|
|
if ax_type == "tonnetz": |
|
axis.set_major_formatter(TonnetzFormatter()) |
|
axis.set_major_locator(FixedLocator(np.arange(6))) |
|
axis.set_label_text("Tonnetz") |
|
|
|
elif ax_type == "chroma": |
|
axis.set_major_formatter(ChromaFormatter(key=key, unicode=unicode)) |
|
degrees = core.key_to_degrees(key) |
|
axis.set_major_locator( |
|
FixedLocator(np.add.outer(12 * np.arange(10), degrees).ravel()) |
|
) |
|
axis.set_label_text("Pitch class") |
|
|
|
elif ax_type == "chroma_h": |
|
if Sa is None: |
|
Sa = 0 |
|
axis.set_major_formatter(ChromaSvaraFormatter(Sa=Sa, unicode=unicode)) |
|
if thaat is None: |
|
|
|
degrees = np.arange(12) |
|
else: |
|
degrees = core.thaat_to_degrees(thaat) |
|
|
|
degrees = np.mod(degrees + Sa, 12) |
|
axis.set_major_locator( |
|
FixedLocator(np.add.outer(12 * np.arange(10), degrees).ravel()) |
|
) |
|
axis.set_label_text("Svara") |
|
|
|
elif ax_type == "chroma_c": |
|
if Sa is None: |
|
Sa = 0 |
|
axis.set_major_formatter( |
|
ChromaSvaraFormatter(Sa=Sa, mela=mela, unicode=unicode) |
|
) |
|
degrees = core.mela_to_degrees(mela) |
|
|
|
degrees = np.mod(degrees + Sa, 12) |
|
axis.set_major_locator( |
|
FixedLocator(np.add.outer(12 * np.arange(10), degrees).ravel()) |
|
) |
|
axis.set_label_text("Svara") |
|
|
|
elif ax_type in ["tempo", "fourier_tempo"]: |
|
axis.set_major_formatter(ScalarFormatter()) |
|
axis.set_major_locator(LogLocator(base=2.0)) |
|
axis.set_label_text("BPM") |
|
|
|
elif ax_type == "time": |
|
axis.set_major_formatter(TimeFormatter(unit=None, lag=False)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Time") |
|
|
|
elif ax_type == "s": |
|
axis.set_major_formatter(TimeFormatter(unit="s", lag=False)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Time (s)") |
|
|
|
elif ax_type == "ms": |
|
axis.set_major_formatter(TimeFormatter(unit="ms", lag=False)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Time (ms)") |
|
|
|
elif ax_type == "lag": |
|
axis.set_major_formatter(TimeFormatter(unit=None, lag=True)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Lag") |
|
|
|
elif ax_type == "lag_s": |
|
axis.set_major_formatter(TimeFormatter(unit="s", lag=True)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Lag (s)") |
|
|
|
elif ax_type == "lag_ms": |
|
axis.set_major_formatter(TimeFormatter(unit="ms", lag=True)) |
|
axis.set_major_locator(MaxNLocator(prune=None, steps=[1, 1.5, 5, 6, 10])) |
|
axis.set_label_text("Lag (ms)") |
|
|
|
elif ax_type == "cqt_note": |
|
axis.set_major_formatter(NoteFormatter(key=key, unicode=unicode)) |
|
|
|
log_C1 = np.log2(core.note_to_hz("C1")) |
|
C_offset = 2.0 ** (log_C1 - np.floor(log_C1)) |
|
axis.set_major_locator(LogLocator(base=2.0, subs=(C_offset,))) |
|
axis.set_minor_formatter(NoteFormatter(key=key, major=False, unicode=unicode)) |
|
axis.set_minor_locator( |
|
LogLocator(base=2.0, subs=C_offset * 2.0 ** (np.arange(1, 12) / 12.0)) |
|
) |
|
axis.set_label_text("Note") |
|
|
|
elif ax_type == "cqt_svara": |
|
axis.set_major_formatter(SvaraFormatter(Sa=Sa, mela=mela, unicode=unicode)) |
|
|
|
sa_offset = 2.0 ** (np.log2(Sa) - np.floor(np.log2(Sa))) |
|
|
|
axis.set_major_locator(LogLocator(base=2.0, subs=(sa_offset,))) |
|
axis.set_minor_formatter( |
|
SvaraFormatter(Sa=Sa, mela=mela, major=False, unicode=unicode) |
|
) |
|
axis.set_minor_locator( |
|
LogLocator(base=2.0, subs=sa_offset * 2.0 ** (np.arange(1, 12) / 12.0)) |
|
) |
|
axis.set_label_text("Svara") |
|
|
|
elif ax_type in ["cqt_hz"]: |
|
axis.set_major_formatter(LogHzFormatter()) |
|
log_C1 = np.log2(core.note_to_hz("C1")) |
|
C_offset = 2.0 ** (log_C1 - np.floor(log_C1)) |
|
axis.set_major_locator(LogLocator(base=2.0, subs=(C_offset,))) |
|
axis.set_major_locator(LogLocator(base=2.0)) |
|
axis.set_minor_formatter(LogHzFormatter(major=False)) |
|
axis.set_minor_locator( |
|
LogLocator(base=2.0, subs=C_offset * 2.0 ** (np.arange(1, 12) / 12.0)) |
|
) |
|
axis.set_label_text("Hz") |
|
|
|
elif ax_type == "fft_note": |
|
axis.set_major_formatter(NoteFormatter(key=key, unicode=unicode)) |
|
|
|
log_C1 = np.log2(core.note_to_hz("C1")) |
|
C_offset = 2.0 ** (log_C1 - np.floor(log_C1)) |
|
axis.set_major_locator(SymmetricalLogLocator(axis.get_transform())) |
|
axis.set_minor_formatter(NoteFormatter(key=key, major=False, unicode=unicode)) |
|
axis.set_minor_locator( |
|
LogLocator(base=2.0, subs=2.0 ** (np.arange(1, 12) / 12.0)) |
|
) |
|
axis.set_label_text("Note") |
|
|
|
elif ax_type == "fft_svara": |
|
axis.set_major_formatter(SvaraFormatter(Sa=Sa, mela=mela, unicode=unicode)) |
|
|
|
log_Sa = np.log2(Sa) |
|
sa_offset = 2.0 ** (log_Sa - np.floor(log_Sa)) |
|
|
|
axis.set_major_locator( |
|
SymmetricalLogLocator(axis.get_transform(), base=2.0, subs=[sa_offset]) |
|
) |
|
axis.set_minor_formatter( |
|
SvaraFormatter(Sa=Sa, mela=mela, major=False, unicode=unicode) |
|
) |
|
axis.set_minor_locator( |
|
LogLocator(base=2.0, subs=sa_offset * 2.0 ** (np.arange(1, 12) / 12.0)) |
|
) |
|
axis.set_label_text("Svara") |
|
|
|
elif ax_type in ["mel", "log"]: |
|
axis.set_major_formatter(ScalarFormatter()) |
|
axis.set_major_locator(SymmetricalLogLocator(axis.get_transform())) |
|
axis.set_label_text("Hz") |
|
|
|
elif ax_type in ["linear", "hz", "fft"]: |
|
axis.set_major_formatter(ScalarFormatter()) |
|
axis.set_label_text("Hz") |
|
|
|
elif ax_type in ["frames"]: |
|
axis.set_label_text("Frames") |
|
|
|
elif ax_type in ["off", "none", None]: |
|
axis.set_label_text("") |
|
axis.set_ticks([]) |
|
|
|
else: |
|
raise ParameterError("Unsupported axis type: {}".format(ax_type)) |
|
|
|
|
|
def __coord_fft_hz(n, sr=22050, n_fft=None, **_kwargs): |
|
"""Get the frequencies for FFT bins""" |
|
if n_fft is None: |
|
n_fft = 2 * (n - 1) |
|
|
|
|
|
basis = core.fft_frequencies(sr=sr, n_fft=n_fft) |
|
return basis |
|
|
|
|
|
def __coord_mel_hz(n, fmin=0, fmax=None, sr=22050, htk=False, **_kwargs): |
|
"""Get the frequencies for Mel bins""" |
|
|
|
if fmin is None: |
|
fmin = 0 |
|
if fmax is None: |
|
fmax = 0.5 * sr |
|
|
|
basis = core.mel_frequencies(n, fmin=fmin, fmax=fmax, htk=htk) |
|
return basis |
|
|
|
|
|
def __coord_cqt_hz(n, fmin=None, bins_per_octave=12, sr=22050, **_kwargs): |
|
"""Get CQT bin frequencies""" |
|
if fmin is None: |
|
fmin = core.note_to_hz("C1") |
|
|
|
|
|
fmin = fmin * 2.0 ** (_kwargs.get("tuning", 0.0) / bins_per_octave) |
|
|
|
|
|
freqs = core.cqt_frequencies( |
|
n, |
|
fmin=fmin, |
|
bins_per_octave=bins_per_octave, |
|
) |
|
|
|
if np.any(freqs > 0.5 * sr): |
|
warnings.warn( |
|
"Frequency axis exceeds Nyquist. " |
|
"Did you remember to set all spectrogram parameters in specshow?", |
|
stacklevel=4, |
|
) |
|
|
|
return freqs |
|
|
|
|
|
def __coord_chroma(n, bins_per_octave=12, **_kwargs): |
|
"""Get chroma bin numbers""" |
|
return np.linspace(0, (12.0 * n) / bins_per_octave, num=n, endpoint=False) |
|
|
|
|
|
def __coord_tempo(n, sr=22050, hop_length=512, **_kwargs): |
|
"""Tempo coordinates""" |
|
basis = core.tempo_frequencies(n + 1, sr=sr, hop_length=hop_length)[1:] |
|
return basis |
|
|
|
|
|
def __coord_fourier_tempo(n, sr=22050, hop_length=512, win_length=None, **_kwargs): |
|
"""Fourier tempogram coordinates""" |
|
if win_length is None: |
|
win_length = 2 * (n - 1) |
|
|
|
|
|
basis = core.fourier_tempo_frequencies( |
|
sr=sr, hop_length=hop_length, win_length=win_length |
|
) |
|
return basis |
|
|
|
|
|
def __coord_n(n, **_kwargs): |
|
"""Get bare positions""" |
|
return np.arange(n) |
|
|
|
|
|
def __coord_time(n, sr=22050, hop_length=512, **_kwargs): |
|
"""Get time coordinates from frames""" |
|
return core.frames_to_time(np.arange(n), sr=sr, hop_length=hop_length) |
|
|
|
|
|
def __same_axes(x_axis, y_axis, xlim, ylim): |
|
"""Check if two axes are the same, used to determine squared plots""" |
|
axes_same_and_not_none = (x_axis == y_axis) and (x_axis is not None) |
|
axes_same_lim = xlim == ylim |
|
return axes_same_and_not_none and axes_same_lim |
|
|
|
|
|
@deprecate_positional_args |
|
def waveshow( |
|
y, |
|
*, |
|
sr=22050, |
|
max_points=11025, |
|
x_axis="time", |
|
offset=0.0, |
|
marker="", |
|
where="post", |
|
label=None, |
|
ax=None, |
|
**kwargs, |
|
): |
|
"""Visualize a waveform in the time domain. |
|
|
|
This function constructs a plot which adaptively switches between a raw |
|
samples-based view of the signal (`matplotlib.pyplot.step`) and an |
|
amplitude-envelope view of the signal (`matplotlib.pyplot.fill_between`) |
|
depending on the time extent of the plot's viewport. |
|
|
|
More specifically, when the plot spans a time interval of less than ``max_points / |
|
sr`` (by default, 1/2 second), the samples-based view is used, and otherwise a |
|
downsampled amplitude envelope is used. |
|
This is done to limit the complexity of the visual elements to guarantee an |
|
efficient, visually interpretable plot. |
|
|
|
When using interactive rendering (e.g., in a Jupyter notebook or IPython |
|
console), the plot will automatically update as the view-port is changed, either |
|
through widget controls or programmatic updates. |
|
|
|
.. note:: When visualizing stereo waveforms, the amplitude envelope will be generated |
|
so that the upper limits derive from the left channel, and the lower limits derive |
|
from the right channel, which can produce a vertically asymmetric plot. |
|
|
|
When zoomed in to the sample view, only the first channel will be shown. |
|
If you want to visualize both channels at the sample level, it is recommended to |
|
plot each signal independently. |
|
|
|
Parameters |
|
---------- |
|
y : np.ndarray [shape=(n,) or (2,n)] |
|
audio time series (mono or stereo) |
|
|
|
sr : number > 0 [scalar] |
|
sampling rate of ``y`` (samples per second) |
|
|
|
max_points : positive integer |
|
Maximum number of samples to draw. When the plot covers a time extent |
|
smaller than ``max_points / sr`` (default: 1/2 second), samples are drawn. |
|
|
|
If drawing raw samples would exceed `max_points`, then a downsampled |
|
amplitude envelope extracted from non-overlapping windows of `y` is |
|
visualized instead. The parameters of the amplitude envelope are defined so |
|
that the resulting plot cannot produce more than `max_points` frames. |
|
|
|
x_axis : str or None |
|
Display of the x-axis ticks and tick markers. Accepted values are: |
|
|
|
- 'time' : markers are shown as milliseconds, seconds, minutes, or hours. |
|
Values are plotted in units of seconds. |
|
|
|
- 's' : markers are shown as seconds. |
|
|
|
- 'ms' : markers are shown as milliseconds. |
|
|
|
- 'lag' : like time, but past the halfway point counts as negative values. |
|
|
|
- 'lag_s' : same as lag, but in seconds. |
|
|
|
- 'lag_ms' : same as lag, but in milliseconds. |
|
|
|
- `None`, 'none', or 'off': ticks and tick markers are hidden. |
|
|
|
ax : matplotlib.axes.Axes or None |
|
Axes to plot on instead of the default `plt.gca()`. |
|
|
|
offset : float |
|
Horizontal offset (in seconds) to start the waveform plot |
|
|
|
marker : string |
|
Marker symbol to use for sample values. (default: no markers) |
|
|
|
See also: `matplotlib.markers`. |
|
|
|
where : string, {'pre', 'mid', 'post'} |
|
This setting determines how both waveform and envelope plots interpolate |
|
between observations. |
|
|
|
See `matplotlib.pyplot.step` for details. |
|
|
|
Default: 'post' |
|
|
|
label : string [optional] |
|
The label string applied to this plot. |
|
Note that the label |
|
|
|
**kwargs |
|
Additional keyword arguments to `matplotlib.pyplot.fill_between` and |
|
`matplotlib.pyplot.step`. |
|
|
|
Note that only those arguments which are common to both functions will be |
|
supported. |
|
|
|
Returns |
|
------- |
|
librosa.display.AdaptiveWaveplot |
|
An object of type `librosa.display.AdaptiveWaveplot` |
|
|
|
See Also |
|
-------- |
|
AdaptiveWaveplot |
|
matplotlib.pyplot.step |
|
matplotlib.pyplot.fill_between |
|
matplotlib.markers |
|
|
|
Examples |
|
-------- |
|
Plot a monophonic waveform with an envelope view |
|
|
|
>>> import matplotlib.pyplot as plt |
|
>>> y, sr = librosa.load(librosa.ex('choice'), duration=10) |
|
>>> fig, ax = plt.subplots(nrows=3, sharex=True) |
|
>>> librosa.display.waveshow(y, sr=sr, ax=ax[0]) |
|
>>> ax[0].set(title='Envelope view, mono') |
|
>>> ax[0].label_outer() |
|
|
|
Or a stereo waveform |
|
|
|
>>> y, sr = librosa.load(librosa.ex('choice', hq=True), mono=False, duration=10) |
|
>>> librosa.display.waveshow(y, sr=sr, ax=ax[1]) |
|
>>> ax[1].set(title='Envelope view, stereo') |
|
>>> ax[1].label_outer() |
|
|
|
Or harmonic and percussive components with transparency |
|
|
|
>>> y, sr = librosa.load(librosa.ex('choice'), duration=10) |
|
>>> y_harm, y_perc = librosa.effects.hpss(y) |
|
>>> librosa.display.waveshow(y_harm, sr=sr, alpha=0.5, ax=ax[2], label='Harmonic') |
|
>>> librosa.display.waveshow(y_perc, sr=sr, color='r', alpha=0.5, ax=ax[2], label='Percussive') |
|
>>> ax[2].set(title='Multiple waveforms') |
|
>>> ax[2].legend() |
|
|
|
Zooming in on a plot to show raw sample values |
|
|
|
>>> fig, (ax, ax2) = plt.subplots(nrows=2, sharex=True) |
|
>>> ax.set(xlim=[6.0, 6.01], title='Sample view', ylim=[-0.2, 0.2]) |
|
>>> librosa.display.waveshow(y, sr=sr, ax=ax, marker='.', label='Full signal') |
|
>>> librosa.display.waveshow(y_harm, sr=sr, alpha=0.5, ax=ax2, label='Harmonic') |
|
>>> librosa.display.waveshow(y_perc, sr=sr, color='r', alpha=0.5, ax=ax2, label='Percussive') |
|
>>> ax.label_outer() |
|
>>> ax.legend() |
|
>>> ax2.legend() |
|
|
|
""" |
|
util.valid_audio(y, mono=False) |
|
|
|
|
|
if y.ndim == 1: |
|
y = y[np.newaxis, :] |
|
|
|
if max_points <= 0: |
|
raise ParameterError( |
|
"max_points={} must be strictly positive".format(max_points) |
|
) |
|
|
|
|
|
axes = __check_axes(ax) |
|
|
|
if "color" not in kwargs: |
|
kwargs.setdefault("color", next(axes._get_lines.prop_cycler)["color"]) |
|
|
|
|
|
|
|
hop_length = max(1, y.shape[-1] // max_points) |
|
y_env = __envelope(y, hop_length) |
|
|
|
|
|
y_bottom, y_top = -y_env[-1], y_env[0] |
|
|
|
times = offset + core.times_like(y, sr=sr, hop_length=1) |
|
|
|
|
|
(steps,) = axes.step( |
|
times[:max_points], y[0, :max_points], marker=marker, where=where, **kwargs |
|
) |
|
|
|
envelope = axes.fill_between( |
|
times[: len(y_top) * hop_length : hop_length], |
|
y_bottom, |
|
y_top, |
|
step=where, |
|
label=label, |
|
**kwargs, |
|
) |
|
adaptor = AdaptiveWaveplot( |
|
times, y[0], steps, envelope, sr=sr, max_samples=max_points |
|
) |
|
|
|
axes.callbacks.connect("xlim_changed", adaptor.update) |
|
|
|
|
|
adaptor.update(axes) |
|
|
|
|
|
__decorate_axis(axes.xaxis, x_axis) |
|
|
|
return adaptor |
|
|