Audio Samples#
Summary
In this tutorial, we’ll see how to load audio samples (either from file urls or numpy arrays) in the front-end and play them with a player or a sampler.
Note: you can download this tutorial as a
Jupyter notebook.
import ipytone
import numpy as np
import matplotlib.pyplot as plt
Matplotlib is building the font cache; this may take a moment.
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[1], line 3
1 import ipytone
2 import numpy as np
----> 3 import matplotlib.pyplot as plt
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/pyplot.py:52
50 from cycler import cycler
51 import matplotlib
---> 52 import matplotlib.colorbar
53 import matplotlib.image
54 from matplotlib import _api
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/colorbar.py:19
16 import numpy as np
18 import matplotlib as mpl
---> 19 from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
20 import matplotlib.artist as martist
21 import matplotlib.patches as mpatches
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/contour.py:13
11 import matplotlib as mpl
12 from matplotlib import _api, _docstring
---> 13 from matplotlib.backend_bases import MouseButton
14 from matplotlib.text import Text
15 import matplotlib.path as mpath
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/backend_bases.py:45
42 import numpy as np
44 import matplotlib as mpl
---> 45 from matplotlib import (
46 _api, backend_tools as tools, cbook, colors, _docstring, text,
47 _tight_bbox, transforms, widgets, get_backend, is_interactive, rcParams)
48 from matplotlib._pylab_helpers import Gcf
49 from matplotlib.backend_managers import ToolManager
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/text.py:16
14 from . import _api, artist, cbook, _docstring
15 from .artist import Artist
---> 16 from .font_manager import FontProperties
17 from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle
18 from .textpath import TextPath, TextToPath # noqa # Logically located here
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/font_manager.py:1548
1544 _log.info("generated new fontManager")
1545 return fm
-> 1548 fontManager = _load_fontmanager()
1549 findfont = fontManager.findfont
1550 get_font_names = fontManager.get_font_names
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/font_manager.py:1542, in _load_fontmanager(try_read_cache)
1540 _log.debug("Using fontManager instance from %s", fm_path)
1541 return fm
-> 1542 fm = FontManager()
1543 json_dump(fm, fm_path)
1544 _log.info("generated new fontManager")
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/font_manager.py:1016, in FontManager.__init__(self, size, weight)
1013 for path in [*findSystemFonts(paths, fontext=fontext),
1014 *findSystemFonts(fontext=fontext)]:
1015 try:
-> 1016 self.addfont(path)
1017 except OSError as exc:
1018 _log.info("Failed to open font file %s: %s", path, exc)
File ~/checkouts/readthedocs.org/user_builds/ipytone/conda/stable/lib/python3.11/site-packages/matplotlib/font_manager.py:1043, in FontManager.addfont(self, path)
1041 self.afmlist.append(prop)
1042 else:
-> 1043 font = ft2font.FT2Font(path)
1044 prop = ttfFontProperty(font)
1045 self.ttflist.append(prop)
KeyboardInterrupt:
Audio buffers#
AudioBuffer and AudioBuffers can be used to
create one or more audio buffers in the front-end from either files or a numpy
arrays.
Example 1: load one sample from a numpy array#
Let’s first create a custom waveform with numpy:
# sine + noise waveform
sample_rate = 44100
duration = 1
frequency = 440
size = int(sample_rate * duration)
factor = frequency * np.pi * 2 / sample_rate
waveform = np.sin(np.arange(size) * factor)
waveform += np.random.uniform(-0.1, 0.1, size=size)
Let’s have a look at the waveform:
plt.plot(waveform[0:1000]);
We can then directly pass the numpy array to the AudioBuffer
constructor:
sine_noise_buffer = ipytone.AudioBuffer(url_or_array=waveform)
Important
Create a buffer from a numpy array is currently limited to samples of a duration less or equal to 10 seconds.
Example 2: load multiple samples from files (urls)#
Let’s create new buffers from wav or mp3 file urls.
Important
Depending on the given urls, creating the buffers may be blocked due to the server CORS policy.
If you want to load local files from within JupyterLab, you can get a valid url by going in the file browser, right-click on the file and “Copy Download Link”.
# These urls are likely wrong if you've downloaded this notebook and run it locally
base_url = "http://localhost:8888/files/docs/"
kick_url = "kick.wav?_xsrf=2%7C1ced6df3%7C0514fa79e3ccfbcffefe3f864a0d4032%7C1654092692"
snare_url = "snare.wav?_xsrf=2%7C1ced6df3%7C0514fa79e3ccfbcffefe3f864a0d4032%7C1654092692"
drum_buffers = ipytone.AudioBuffers(
base_url=base_url,
urls={"kick": kick_url, "snare": snare_url},
)
Single buffers may be accessed via the buffers property, e.g.,
drum_buffers.buffers["kick"]
Buffer attributes#
Audio buffers have a few read-only attributes like loaded, duration,
length, sample_rate, etc.
# whether all drum buffers are loaded in the front-end
drum_buffers.loaded
# clip duration (in seconds)
drum_buffers.buffers["kick"].duration
# clip length in number of samples
drum_buffers.buffers["kick"].length
And a reverse property that can be read or written:
drum_buffers.buffers["kick"].reverse
Player#
Player is a source audio node for playing samples.
Let’s create a new player from the custom sine/noise buffer created above:
player = ipytone.Player(sine_noise_buffer).to_destination()
Note
We can also directly pass an url to Player,
which will automatically create an audio buffer.
Like any other source, it has start() ant stop() methods:
player.start().stop("+3")
It also has some additional properties, e.g., to add fade-in/out, play it looped, change its playback rate, etc.
player.loop = True
player.fade_in = 0.2
player.playback_rate = 0.5
player.start().stop("+3")
For convenience, Players can be used to create multiple
Player instances at once.
Let’s create players from the drum buffers created above:
drums = ipytone.Players(drum_buffers.buffers).to_destination()
We can get the individual players with get_player():
drums.get_player("kick").start().stop("+1")
drums.get_player("snare").start("+0.5").stop("+1.5")
Sampler#
As an alternative to Player, Sampler is an
instrument based on samples.
For example, let’s create a sampler with the sine/noise buffer created above. The name of the buffers must correspond to something that can be interpreted like a musical note.
sampler = ipytone.Sampler({"A4": sine_noise_buffer}, volume=-10).to_destination()
Being an instrument, Sampler provides the same interface than
any other instrument (see the instruments tutorial).
When playing a note, the sampler selects the closest sample and adapts the pitch
to generate the corresponding note.
sampler.trigger_attack_release("C2", 0.2)
sampler.trigger_attack_release("A2", 0.2, time="+0.2")
sampler.trigger_attack_release("C3", 0.2, time="+0.4")
sampler.trigger_attack_release("C4", 0.2, time="+0.6")
Sampler is a polyphonic instrument:
# trigger a chord
sampler.trigger_attack_release(["C4", "E4", "G4"], 0.5)
Dispose buffers#
Like for other audio nodes, audio buffers should be disposed if they are not used anymore.
sine_noise_buffer.dispose()
drum_buffers.dispose()
# note: dispose the player(s) or the sampler
# would also dispose the buffers
player.dispose()
drums.dispose()
sampler.dispose()