import numpy as np
from typing import Tuple
from ..utils import normalize_signal, fade_signal, smooth_weights
from .methods import generate_shepard_tone
[docs]
def sonify_chroma_vector(chroma_vector: np.ndarray,
pitch_range: Tuple[int, int] = (20, 108),
filter: bool = False,
f_center: float = 440.0,
octave_cutoff: int = 1,
tuning_frequency: float = 440.0,
fading_duration: float = 0.05,
sonification_duration: int = None,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a chroma vector using sound synthesis based on shepard tones.
The sound can be changed either by the filter option or by the specified pitch-range.
Both options can also be used in combination. Using the filter option shapes the spectrum
like a bell curve centered around the center frequency, while the octave cutoff determines
at which octave the amplitude of the corresponding sinusoid is 0.5.
Parameters
----------
chroma_vector: np.ndarray (np.float32 / np.float64) [shape=(N, 12)]
Chroma vector to sonify.
pitch_range: Tuple[int, int], default = [20,108]
Determines the pitches to encounter for shepard tones.
filter: bool, default: False
Enables filtering of shepard tones.
f_center : float, default: 440.0
Determines filter center frequency, in Hertz.
octave_cutoff: int, default: 1
Determines the width of the filter.
tuning_frequency: float, default: 440.0
Tuning frequency, in Hertz.
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 normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
chroma_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified chroma vector.
"""
assert len(chroma_vector) == 12, f'The chroma vector must have length 12.'
# Determine length of sonification
num_samples = sonification_duration
# Initialize sonification
chroma_sonification = np.zeros(num_samples)
for pitch_class in range(12):
if chroma_vector[pitch_class] > 0:
shepard_tone = generate_shepard_tone(pitch_class=pitch_class,
pitch_range=pitch_range,
filter=filter,
f_center=f_center,
octave_cutoff=octave_cutoff,
gain=chroma_vector[pitch_class],
duration=num_samples / fs,
tuning_frequency=tuning_frequency,
fading_duration=fading_duration,
fs=fs)
chroma_sonification += shepard_tone
chroma_sonification = fade_signal(chroma_sonification, fading_duration=fading_duration, fs=fs)
chroma_sonification = normalize_signal(chroma_sonification) if normalize else chroma_sonification
return chroma_sonification
[docs]
def sonify_chromagram(chromagram: np.ndarray,
H: int = 0,
pitch_range: Tuple[int, int] = (20, 108),
filter: bool = False,
f_center: float = 440.0,
octave_cutoff: int = 1,
tuning_frequency: float = 440.0,
fading_duration: float = 0.05,
sonification_duration: int = None,
normalize: bool = True,
fs: int = 22050) -> np.ndarray:
"""Sonifies a chromagram using sound synthesis based on shepard tones.
The sound can be changed either by the filter option or by the specified pitch-range.
Both options can also be used in combination.
Using the filter option shapes the spectrum like a bell curve centered around the center frequency,
while the octave cutoff determines at which octave the amplitude of the corresponding sinusoid is 0.5.
Parameters
----------
chromagram: np.ndarray (np.float32 / np.float64) [shape=(N, 12)]
Chromagram to sonify.
H: int, default = 0
Hop size of STFT used to calculate chromagram.
pitch_range: Tuple[int, int], default = [20,108]
Determines the pitch range to encounter for shepard tones.
filter: bool, default: False
Enables filtering of shepard tones.
f_center : float, default: 440.0
Determines filter center frequency, in Hertz.
octave_cutoff: int, default: 1
Determines the width of the filter.
For octave_cutoff of 1, the magnitude of the filter reaches 0.5 at half the center_frequency and twice the center_frequency.
tuning_frequency: float, default: 440.0
Tuning frequency, in Hertz.
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 normalized to [-1,1].
fs: int, default = 22050
Sampling rate, in samples per seconds.
Returns
-------
chroma_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )]
Sonified chromagram.
"""
if chromagram.shape[0] != 12:
raise IndexError(f'The chromagram must have shape 12xN.')
# Compute frame rate
frame_rate = fs / H
# Determine length of sonification
num_samples = int(chromagram.shape[1] * fs / frame_rate)
# Initialize sonification
chroma_sonification = np.zeros(num_samples)
for pitch_class in range(12):
if np.sum(np.abs(chromagram[pitch_class, :])) > 0:
weighting_vector = np.repeat(chromagram[pitch_class, :], H)
weighting_vector_smoothed = smooth_weights(weights=weighting_vector, fading_samples=int(H / 8))
shepard_tone = generate_shepard_tone(pitch_class=pitch_class,
pitch_range=pitch_range,
filter=filter,
f_center=f_center,
octave_cutoff=octave_cutoff,
gain=1,
duration=num_samples / fs,
tuning_frequency=tuning_frequency,
fading_duration=fading_duration,
fs=fs)
chroma_sonification += (shepard_tone * weighting_vector_smoothed)
chroma_sonification = fade_signal(chroma_sonification, fading_duration=fading_duration, fs=fs)
chroma_sonification = normalize_signal(chroma_sonification) if normalize else chroma_sonification
if sonification_duration is not None:
if len(chroma_sonification) > sonification_duration:
chroma_sonification = chroma_sonification[:sonification_duration]
else:
tmp = np.zeros(sonification_duration)
tmp[:len(chroma_sonification)] = np.copy(chroma_sonification)
chroma_sonification = tmp
return chroma_sonification