import numpy as np
from typing import List, Tuple
from ..utils import normalize_signal, fade_signal
from .methods import generate_click
[docs]
def sonify_tse_clicks(time_positions: np.ndarray = None,
click_pitch: int = 69,
click_fading_duration: float = 0.25,
click_amplitude: float = 1.0,
offset_relative: float = 0.0,
sonification_duration: int = None,
fading_duration: float = 0.05,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies array of time positions with clicks.
Parameters
----------
time_positions: np.ndarray (np.float32 / np.float64) [shape=(N, )]
Array with time positions for clicks.
click_pitch: int, default = 69
Pitch for click signal.
click_fading_duration: float, default = 0.25
Fading duration for click signal, in seconds
click_amplitude: float, default = 1.0
Amplitude for click signal.
offset_relative: float, default = 0.0
Relative offset for the beginning of a click.
0 indicates that the beginning of the click event is at the time position,
1 indicates the ending of the click event corresponds to the time position.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the sonification, in seconds.
normalize: bool, default = True
Determines if output signal is max-normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
tse_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified time positions.
"""
assert 0 <= click_pitch <= 127, f'Pitch is out of range [0,127].'
assert 0 <= offset_relative <= 1, f'Relative offset is out of range [0,1].'
num_samples = int((time_positions[-1] + click_fading_duration) * fs)
if sonification_duration is None:
sonification_duration = num_samples
else:
if sonification_duration < num_samples:
duration_in_sec = sonification_duration / fs
time_positions = time_positions[time_positions < duration_in_sec]
num_samples = int(sonification_duration * fs)
click = generate_click(pitch=click_pitch,
click_fading_duration=click_fading_duration,
amplitude=click_amplitude)
tse_sonification = __sonify_tse_with_sound_event(click,
offset_relative,
time_positions,
num_samples,
fs,
fading_duration,
normalize)
return tse_sonification[:sonification_duration]
[docs]
def sonify_tse_sample(time_positions: np.ndarray = None,
sample: np.ndarray = None,
offset_relative: float = 0.0,
sonification_duration: int = None,
fading_duration: float = 0.05,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies time positions with warped versions of a custom sample (e.g., metronome sounds).
Parameters
----------
time_positions: np.ndarray (np.float32 / np.float64) [shape=(N, )]
Array with time positions for clicks.
sample: np.ndarray (np.float32 / np.float64) [shape=(K, )]
Sample to be used.
offset_relative: float, default = 0.0
Relative offset coefficient for the beginning of a click.
0 indicates that the beginning of the click event is at the time position. 1 indicates the ending of the click
event corresponds to the time position.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the sonification, in seconds.
normalize: bool, default = True
Determines if output signal is max-normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
tse_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified time positions.
"""
sample_len = len(sample)
num_samples = int((time_positions[-1]) * fs) + sample_len
assert sample_len < time_positions[-1] * fs, f'The custom sample cannot be longer than the annotations.'
if sonification_duration is not None:
assert sample_len < sonification_duration, 'The custom sample cannot be longer than the sonification_duration.'
if sonification_duration is None:
sonification_duration = num_samples
else:
if sonification_duration < num_samples:
duration_in_sec = sonification_duration / fs
time_positions = time_positions[time_positions < duration_in_sec]
num_samples = sonification_duration
tse_sonification = __sonify_tse_with_sound_event(sample,
offset_relative,
time_positions,
num_samples,
fs,
fading_duration,
normalize)
return tse_sonification[:sonification_duration]
[docs]
def sonify_tse_multiple_clicks(times_pitches: List[Tuple[np.ndarray, int]] = None,
click_fading_duration: float = 0.25,
click_amplitude: float = 1.0,
offset_relative: float = 0.0,
sonification_duration: int = None,
fading_duration: float = 0.05,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Given multiple arrays in form of a list, this function creates the sonification of different sources.
Parameters
----------
times_pitches: List[Tuple[np.ndarray, int]]
List of tuples comprising the time positions and pitches of the clicks
sonification_duration: int, default = None
Duration of the output waveform, given in samples.
click_fading_duration: float, default = 0.25
Duration for click signal.
click_amplitude: float, default = 1.0
Amplitude for click signal.
offset_relative: float, default = 0.0
Relative offset coefficient for the beginning of the given audio sample.
0 indicates that the beginning of the sample is at the time position. 1 indicates the ending of the sample
corresponding to the time position.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the sonification, in seconds.
normalize: bool, default = True
Determines if output signal is max-normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
tse_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified time positions.
"""
if sonification_duration is None:
max_duration = 0
for times_pitch in times_pitches:
sonification_duration = times_pitch[0][-1]
max_duration = sonification_duration if sonification_duration > max_duration else max_duration
sonification_duration = int(np.ceil(fs * (max_duration + click_fading_duration)))
tse_sonification = np.zeros(sonification_duration)
for times_pitch in times_pitches:
time_positions, pitch = times_pitch
tse_sonification += sonify_tse_clicks(time_positions=time_positions,
click_pitch=pitch,
click_fading_duration=click_fading_duration,
click_amplitude=click_amplitude,
offset_relative=offset_relative,
sonification_duration=sonification_duration,
fs=fs)
tse_sonification = fade_signal(tse_sonification, fs=fs, fading_duration=fading_duration)
tse_sonification = normalize_signal(tse_sonification) if normalize else tse_sonification
return tse_sonification
[docs]
def sonify_tse_multiple_samples(times_samples: List[Tuple[np.ndarray, np.ndarray]] = None,
offset_relative: float = 0.0,
sonification_duration: int = None,
fading_duration: float = 0.05,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Given multiple arrays in form of a list, this function creates the sonification of different sources.
Parameters
----------
times_samples: List[Tuple[np.ndarray, np.ndarray]]
List of tuples comprising the time positions and samples
offset_relative: float, default = 0.0
Relative offset coefficient for the beginning of the given audio sample.
0 indicates that the beginning of the sample is at the time position. 1 indicates the ending of the sample
corresponding to the time position.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the sonification, in seconds.
normalize: bool, default = True
Determines if output signal is max-normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
tse_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified waveform in form of a 1D Numpy array.
"""
if sonification_duration is None:
max_duration = 0
max_sample_duration_samples = 0
for time_sample in times_samples:
time_positions, sample = time_sample
sonification_duration = time_positions[-1]
duration_sample_samples = len(sample)
max_duration = sonification_duration if sonification_duration > max_duration else max_duration
max_sample_duration_samples = duration_sample_samples \
if duration_sample_samples > max_sample_duration_samples else max_sample_duration_samples
sonification_duration = int(np.ceil(fs * max_duration)) + max_sample_duration_samples
tse_sonification = np.zeros(sonification_duration)
for times_sample in times_samples:
time_positions, sample = times_sample
tse_sonification += sonify_tse_sample(time_positions=time_positions,
sample=sample,
offset_relative=offset_relative,
sonification_duration=sonification_duration,
fs=fs)
tse_sonification = fade_signal(tse_sonification, fs=fs, fading_duration=fading_duration)
tse_sonification = normalize_signal(tse_sonification) if normalize else tse_sonification
return tse_sonification
def __sonify_tse_with_sound_event(sound_event: np.ndarray,
offset_relative: float,
time_positions: np.ndarray,
num_samples: int,
fs: int,
fading_duration: float,
normalize: bool) -> np.ndarray:
"""Sonify with sound events (e.g., beats, downbeats, etc.)
Parameters
----------
sound_event: np.ndarray (np.float32 / np.float64) [shape=(K, )]
A click signal or sample loaded from the disk
offset_relative: float, default = 0.0
Relative offset for the beginning of a sound event.
0 indicates that the beginning of the sound event is at the time position,
1 indicates the ending of the sound event corresponds to the time position.:
time_positions: np.ndarray (np.float32 / np.float64) [shape=(N, )]
Array with time positions of the annotations.
num_samples: int
Number of samples of the output signals
fs: int
Sampling rate
fading_duration: float
Determines duration of fade-in and fade-out at beginning and end of the sonification, in seconds.
normalize: bool, default = True
Determines if output signal is max-normalized to [-1,1].
Returns
-------
tse_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified signal with multiple sound events, given the time positions
"""
tse_sonification = np.zeros(num_samples)
num_click_samples = len(sound_event)
offset_samples = int(offset_relative * num_click_samples)
for idx, time_position in enumerate(time_positions):
start_samples = int(time_position * fs) - offset_samples
end_samples = start_samples + num_click_samples
if start_samples < 0:
if end_samples <= 0:
continue
tse_sonification[:end_samples] += sound_event[-end_samples:]
else:
tse_sonification[start_samples:end_samples] += sound_event
tse_sonification = fade_signal(tse_sonification, fs=fs, fading_duration=fading_duration)
tse_sonification = normalize_signal(tse_sonification) if normalize else tse_sonification
return tse_sonification