import numpy as np
import pandas as pd
from ..utils import format_df, warp_sample, normalize_signal, fade_signal
from .methods import generate_click, generate_tone_additive_synthesis, generate_tone_fm_synthesis
[docs]
def sonify_pianoroll_additive_synthesis(pianoroll_df: pd.DataFrame,
partials: np.ndarray = np.array([1]),
partials_amplitudes: np.ndarray = None,
partials_phase_offsets: np.ndarray = None,
tuning_frequency: float = 440.0,
signal_fading_duration: float = 0.05,
note_fading_duration: float = 0.01,
sonification_duration: float = None,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a piano-roll with additive synthesis.
The DataFrame representation is assumed to contain row-wise pitch events described by start, duration or end
and the corresponding pitch.
The sonification is based on additive synthesis, where parameters partials, partials_amplitudes and
partials_phase_offsets can be used to shape the sound.
Parameters
----------
pianoroll_df: pd.DataFrame
Dataframe containing pitch-event information with columns ['start', 'duration', 'pitch', 'velocity', 'label'].
partials: np.ndarray (np.float32 / np.float64) [shape=(N, )], default = [1]
Array containing the desired partials of the fundamental frequencies for sonification.
An array [1] leads to sonification with only the fundamental frequency,
while an array [1,2] leads to sonification with the fundamental frequency and twice the fundamental frequency.
partials_amplitudes: np.ndarray (np.float32 / np.float64) [shape=(N, )], default = None
Array containing the amplitudes for partials.
An array [1,0.5] causes the first partial to have amplitude 1,
while the second partial has amplitude 0.5.
When not defined, the amplitudes for all partials are set to 1.
partials_phase_offsets: np.ndarray (np.float32 / np.float64) [shape=(N, )], default = None
Array containing the phase offsets for partials.
When not defined, the phase offsets for all partials are set to 0.
tuning_frequency: float, default = 440.0
Tuning frequency, in Hertz.
sonification_duration: float, default = None
Determines duration of sonification, in samples
signal_fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the final sonification, in seconds.
note_fading_duration: float, default = 0.01
Determines duration of fade-in and fade-out at beginning and end of each note event, in seconds.
normalize: bool, default = True
Determines if output signal is normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
pianoroll_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified piano-roll.
"""
pianoroll_df, pianoroll_sonification = __init_pianoroll_sonification(pianoroll_df, fs, sonification_duration)
if 'velocity' in list(pianoroll_df.columns) and pianoroll_df['velocity'].max() > 1:
pianoroll_df['velocity'] /= pianoroll_df['velocity'].max()
for i, r in pianoroll_df.iterrows():
start_samples = int(r['start'] * fs)
duration_samples = int(r['duration'] * fs)
amplitude = r['velocity'] if 'velocity' in r else 1.0
pianoroll_sonification[start_samples:start_samples + duration_samples] += \
generate_tone_additive_synthesis(pitch=r['pitch'],
partials=partials,
partials_amplitudes=partials_amplitudes,
partials_phase_offsets=partials_phase_offsets,
gain=amplitude,
duration=r['duration'],
tuning_frequency=tuning_frequency,
fading_duration=note_fading_duration,
fs=fs)
pianoroll_sonification = fade_signal(pianoroll_sonification, fs=fs, fading_duration=signal_fading_duration)
pianoroll_sonification = normalize_signal(pianoroll_sonification) if normalize else pianoroll_sonification
return pianoroll_sonification
[docs]
def sonify_pianoroll_clicks(pianoroll_df: pd.DataFrame,
tuning_frequency: float = 440.0,
sonification_duration: float = None,
signal_fading_duration: float = 0.05,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a piano-roll with clicks.
The DataFrame representation is assumed to contain row-wise pitch events described by start, duration or end
and the corresponding pitch.
For sonification, coloured clicks are used.
Parameters
----------
pianoroll_df: pd.DataFrame
Dataframe containing pitch-event information with columns ['start', 'duration', 'pitch', 'velocity', 'label'].
tuning_frequency: float, default = 440.0
Tuning Frequency, in Hertz
sonification_duration: float, default = None
Determines duration of sonification, in seconds.
signal_fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the final sonification, in seconds.
normalize: bool, default = True
Determines if output signal is normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
pianoroll_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified waveform in form of a 1D Numpy array.
"""
pianoroll_df, pianoroll_sonification = __init_pianoroll_sonification(pianoroll_df, fs, sonification_duration)
if 'velocity' in list(pianoroll_df.columns) and pianoroll_df['velocity'].max() > 1:
pianoroll_df['velocity'] /= pianoroll_df['velocity'].max()
for i, r in pianoroll_df.iterrows():
start_samples = int(r['start'] * fs)
duration_samples = int(r['duration'] * fs)
amplitude = r['velocity'] if 'velocity' in r else 1.0
pianoroll_sonification[start_samples:start_samples + duration_samples] += \
generate_click(pitch=r['pitch'],
amplitude=amplitude,
tuning_frequency=tuning_frequency,
click_fading_duration=r['duration'],
fs=fs)
pianoroll_sonification = fade_signal(pianoroll_sonification, fs=fs, fading_duration=signal_fading_duration)
pianoroll_sonification = normalize_signal(pianoroll_sonification) if normalize else pianoroll_sonification
return pianoroll_sonification
[docs]
def sonify_pianoroll_sample(pianoroll_df: pd.DataFrame,
sample: np.ndarray = None,
reference_pitch: int = 69,
sonification_duration: int = None,
signal_fading_duration: float = 0.05,
note_fading_duration: float = 0.01,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a piano-roll based on custom audio samples.
The DataFrame representation is assumed to contain row-wise pitch events described by start, duration or end
and the corresponding pitch. For sonification, warped versions of the given sample are used.
Parameters
----------
pianoroll_df: pd.DataFrame
Dataframe containing pitch-event information with columns ['start', 'duration', 'pitch', 'velocity', 'label'].
sample: np.ndarray (np.float32 / np.float64) [shape=(K, )]
Sample to use for sonification.
reference_pitch: int, default = 69
Original pitch of the sample.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
signal_fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the final sonification, in seconds.
note_fading_duration: float, default = 0.01
Determines duration of fade-in and fade-out at beginning and end of each note event, in seconds.
normalize: bool, default = True
Determines if output signal is normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
pianoroll_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified piano-roll.
"""
pianoroll_df, pianoroll_sonification = __init_pianoroll_sonification(pianoroll_df, fs,sonification_duration)
if 'velocity' in list(pianoroll_df.columns) and pianoroll_df['velocity'].max() > 1:
pianoroll_df['velocity'] /= pianoroll_df['velocity'].max()
else:
pianoroll_df['velocity'] = 1
for i, r in pianoroll_df.iterrows():
start_samples = int(r['start'] * fs)
warped_sample = warp_sample(sample=sample,
reference_pitch=reference_pitch,
target_pitch=r['pitch'],
target_duration_sec=r['duration'],
gain=r['velocity'],
fading_duration=note_fading_duration,
fs=fs)
pianoroll_sonification[start_samples:start_samples + len(warped_sample)] += warped_sample
pianoroll_sonification = fade_signal(pianoroll_sonification, fs=fs, fading_duration=signal_fading_duration)
pianoroll_sonification = normalize_signal(pianoroll_sonification) if normalize else pianoroll_sonification
return pianoroll_sonification
[docs]
def sonify_pianoroll_fm_synthesis(pianoroll_df: pd.DataFrame,
mod_rate_relative: float = 0.0,
mod_amp: float = 0.0,
tuning_frequency: float = 440.0,
sonification_duration: int = None,
signal_fading_duration: float = 0.05,
note_fading_duration: float = 0.01,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a piano-roll with frequency modulation (FM) synthesis.
The DataFrame representation is assumed to contain row-wise pitch events described by start, duration or end
and the corresponding pitch. The sonification is based on FM synthesis, where parameters mod_rate_relative and
mod_amp can be used to shape the sound.
Parameters
----------
pianoroll_df: pd.DataFrame
Dataframe containing pitch-event information with columns ['start', 'duration', 'pitch', 'velocity', 'label'].
mod_rate_relative: float, default = 0.0
Determines the modulation frequency as multiple or fraction of the frequency for the given pitch.
mod_amp: float, default = 0.0
Determines the amount of modulation in the generated signal.
tuning_frequency: float, default = 440.0
Tuning frequency in Hertz.
sonification_duration: int, default = None
Determines duration of sonification, in samples.
signal_fading_duration: float, default = 0.05
Determines duration of fade-in and fade-out at beginning and end of the final sonification, in seconds.
note_fading_duration: float, default = 0.01
Determines duration of fade-in and fade-out at beginning and end of each note event, in seconds.
normalize: bool, default = True
Determines if output signal is normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
pianoroll_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified piano-roll.
"""
pianoroll_df, pianoroll_sonification = __init_pianoroll_sonification(pianoroll_df, fs, sonification_duration)
if 'velocity' in list(pianoroll_df.columns) and pianoroll_df['velocity'].max() > 1:
pianoroll_df['velocity'] /= pianoroll_df['velocity'].max()
for i, r in pianoroll_df.iterrows():
start_samples = int(r['start'] * fs)
duration_samples = int(r['duration'] * fs)
amplitude = r['velocity'] if 'velocity' in r else 1.0
pianoroll_sonification[start_samples:start_samples + duration_samples] += \
generate_tone_fm_synthesis(pitch=r['pitch'],
modulation_rate_relative=mod_rate_relative,
modulation_amplitude=mod_amp,
gain=amplitude,
duration=r['duration'],
tuning_frequency=tuning_frequency,
fading_duration=note_fading_duration,
fs=fs)
pianoroll_sonification = fade_signal(pianoroll_sonification, fs=fs, fading_duration=signal_fading_duration)
pianoroll_sonification = normalize_signal(pianoroll_sonification) if normalize else pianoroll_sonification
return pianoroll_sonification
def __init_pianoroll_sonification(pianoroll_df: pd.DataFrame,
fs: int,
sonification_duration: int = None):
pianoroll_df = format_df(pianoroll_df)
num_samples = int(pianoroll_df['end'].max() * fs)
if sonification_duration is None:
sonification_duration = int(pianoroll_df['end'].max() * fs)
sonification_duration_secs = sonification_duration / fs
# if sonification_duration is less than num_samples, crop the arrays
if sonification_duration < num_samples:
pianoroll_df = pianoroll_df[pianoroll_df['start'] < sonification_duration_secs]
pianoroll_df.loc[pianoroll_df['end'] > sonification_duration_secs, 'end'] = sonification_duration_secs
pianoroll_df['duration'] = pianoroll_df['end'] - pianoroll_df['start']
pianoroll_sonification = np.zeros(sonification_duration)
return pianoroll_df, pianoroll_sonification