Signal Basics

audiotoolbox uses the audiotoolbox.Signal class to represent stimuli in the time domain. This class provides an easy-to-use method for modifying and analyzing signals.

Creating Signals

An empty, 1-second long signal with two channels at 48 kHz is initialized by calling:

>>> import audiotoolbox as audio
>>> import numpy as np
>>>
>>> signal = audio.Signal(n_channels=2, duration=1, fs=48000)

audiotoolbox supports an unlimited number of channels, which can also be arranged across multiple dimensions. For example:

>>> signal = audio.Signal(n_channels=(2, 3), duration=1, fs=48000)

By default, modifications are applied to all channels simultaneously. The following two lines add 1 to all samples in all channels:

>>> signal = audio.Signal(n_channels=2, duration=1, fs=48000)
>>> signal += 1

Individual channels can be addressed easily using the audiotoolbox.Signal.ch indexer:

>>> signal = audio.Signal(n_channels=(2, 3), duration=1, fs=48000)
>>> signal.ch[0] += 1

This will add 1 only to the first channel group. The ch indexer also allows for slicing:

>>> signal = audio.Signal(n_channels=3, duration=1, fs=48000)
>>> signal.ch[1:] += 1

This will add 1 to all but the first channel. Internally, the audiotoolbox.Signal class is a numpy.ndarray where the first dimension is the time axis (number of samples). The subsequent dimensions define the channels:

>>> signal = audio.Signal(n_channels=(2, 3), duration=1, fs=48000)
>>> signal.shape
(48000, 2, 3)

The number of samples and the number of channels can be accessed through properties of the audiotoolbox.Signal class:

>>> signal = audio.Signal(n_channels=(2, 3), duration=1, fs=48000)
>>> print(f'No. of samples: {signal.n_samples}, No. of channels: {signal.n_channels}')
No. of samples: 48000, No. of channels: (2, 3)

The time axis can be accessed directly using the audiotoolbox.Signal.time property:

>>> signal = audio.Signal(n_channels=1, duration=1, fs=48000)
>>> signal.time
array([0.00000000e+00, 2.08333333e-05, 4.16666667e-05, ...,
       9.99937500e-01, 9.99958333e-01, 9.99979167e-01])

It’s important to understand that all modifications are in-place, meaning that calling a method does not return a changed copy of the signal but directly changes the signal’s data:

>>> signal = audio.Signal(n_channels=1, duration=1, fs=48000)
>>> signal.add_tone(frequency=500)
>>> signal.var()
0.49999999999999994

Creating a copy of a Signal requires the explicit use of the audiotoolbox.Signal.copy() method. The audiotoolbox.Signal.copy_empty() method can be used to create an empty copy with the same shape as the original:

>>> signal = audio.Signal(n_channels=1, duration=1, fs=48000)
>>> signal2 = signal.copy_empty()