Skip to content

Commit

Permalink
Merge f0e0a8f into bb682e9
Browse files Browse the repository at this point in the history
  • Loading branch information
bmcfee committed Apr 20, 2015
2 parents bb682e9 + f0e0a8f commit 458b93b
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ New features
- `feature.rmse`
- `feature.zero_crossing_rate`
- `feature.tonnetz`
- Added `librosa.display.waveplot`


Other changes
Expand Down
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ New features
- ``feature.zero_crossing_rate``
- ``feature.tonnetz``

- Added ``display.waveplot``

Other changes

- Internal refactoring and restructuring of submodules
Expand Down
121 changes: 121 additions & 0 deletions librosa/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
:toctree: generated/
specshow
waveplot
time_ticks
cmap
"""

import numpy as np
Expand All @@ -20,6 +22,7 @@

from . import cache
from . import core
from . import util

_HAS_SEABORN = False
try:
Expand Down Expand Up @@ -208,6 +211,124 @@ def cmap(data, use_sns=True, robust=True):
return plt.get_cmap('coolwarm')


def waveplot(y, sr=22050, max_points=5e4, x_axis='time', offset=0.0, **kwargs):
'''Plot the amplitude 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 are optionally downsampled.
Parameters
----------
y : np.ndarray [shape=(n,) or (2,n)]
audio time series (mono or stereo)
sr : int > 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
kwargs
Additional keyword arguments to `matplotlib.pyplot.fill_between`
Returns
-------
ax : 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())
>>> plt.figure()
>>> librosa.display.waveplot(y, sr=sr)
>>> plt.title('Monophonic')
Or a stereo waveform
>>> y, sr = librosa.load(librosa.util.example_audio_file(), mono=False)
>>> plt.figure()
>>> 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.figure()
>>> 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')
'''

util.valid_audio(y, mono=False)

target_sr = sr

if max_points is not None:
if max_points < y.shape[-1]:
target_sr = min(256, (sr * y.shape[-1]) // max_points)

if y.ndim == 1:
y = core.resample(y, sr, target_sr, res_type='linear')
else:
y = np.vstack([core.resample(_, sr, target_sr) for _ in y])

y = np.abs(y)

if y.ndim > 1:
y_top = y[0]
y_bottom = -y[1]
else:
y_top = y
y_bottom = -y

ax = plt.gca()
kwargs.setdefault('color', next(ax._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 = ax.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))
elif x_axis is None or x_axis in ['off', 'none']:
plt.xticks([])
else:
raise ValueError('Unknown x_axis value: {}'.format(x_axis))

return out


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,
**kwargs):
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 35 additions & 6 deletions tests/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@


@nottest
def get_spec():

__EXAMPLE_FILE = 'data/test1_22050.wav'

y, sr = librosa.load(__EXAMPLE_FILE)
def get_spec(y, sr):

C = librosa.cqt(y, sr=sr)
return librosa.stft(y), C, sr

S, C, sr = get_spec()

__EXAMPLE_FILE = 'data/test1_22050.wav'
y, sr = librosa.load(__EXAMPLE_FILE)
S, C, sr = get_spec(y, sr)
S_abs = np.abs(S)
S_signed = np.abs(S) - np.median(np.abs(S))
S_bin = S_signed > 0
Expand Down Expand Up @@ -267,6 +266,36 @@ def test_time_scales_explicit():
librosa.display.time_ticks(locs, times, fmt='h')


@image_comparison(baseline_images=['waveplot_mono'], extensions=['png'])
def test_waveplot_mono():

plt.figure()
plt.subplot(3, 1, 1)
librosa.display.waveplot(y, sr=sr, max_points=None, x_axis='off')

plt.subplot(3, 1, 2)
librosa.display.waveplot(y, sr=sr, x_axis='off')

plt.subplot(3, 1, 3)
librosa.display.waveplot(y, sr=sr, x_axis='time')


@image_comparison(baseline_images=['waveplot_stereo'], extensions=['png'])
def test_waveplot_stereo():

ys = np.vstack([y[np.newaxis, :], 2 * y[np.newaxis, :]])

plt.figure()
librosa.display.waveplot(ys, sr=sr)


@raises(ValueError)
def test_unknown_wavaxis():

plt.figure()
librosa.display.waveplot(y, sr=sr, x_axis='something not in the axis map')


def test_unknown_axis():

@raises(ValueError)
Expand Down

0 comments on commit 458b93b

Please sign in to comment.