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

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]);
_images/b417af1520fe1c215270a2118f0974549e97b8bf24f7d6b457995cfef750903a.png

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"]
AudioBuffer(loaded=False)

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
False
# clip duration (in seconds)
drum_buffers.buffers["kick"].duration
0.0
# clip length in number of samples
drum_buffers.buffers["kick"].length
0

And a reverse property that can be read or written:

drum_buffers.buffers["kick"].reverse
False

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")
Player()

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")
Player()

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")
Player()

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()

Sampler is a polyphonic instrument:

# trigger a chord
sampler.trigger_attack_release(["C4", "E4", "G4"], 0.5)
Sampler()

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()
Sampler(disposed=True)