Source code for libsoni.core.spectrogram

from concurrent.futures import ProcessPoolExecutor
import os

import numpy as np

from ..utils import normalize_signal, fade_signal, smooth_weights
from .methods import generate_sinusoid


[docs] def sonify_spectrogram(spectrogram: np.ndarray, frequency_coefficients: np.ndarray = None, time_coefficients: np.ndarray = None, fading_duration: float = 0.05, sonification_duration: int = None, normalize: bool = True, fs: int = 22050) -> np.ndarray: """Sonifies a spectrogram using sinusoids. Parameters ---------- spectrogram: np.ndarray (np.float32 / np.float64) [shape=(N, K)] Spectrogram to be sonified. frequency_coefficients: np.ndarray (np.float32 / np.float64) [shape=(N, )], default = None Array containing frequency coefficients, in Hertz. time_coefficients: np.ndarray (np.float32 / np.float64) [shape=(K, )], default = None Array containing time coefficients, in seconds. 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 ------- spectrogram_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )] Sonified spectrogram. """ __check_spect_shape(spectrogram, len(frequency_coefficients), len(time_coefficients)) # Calculate Hop size from time_coefficients if not explicitly given H = int((time_coefficients[1] - time_coefficients[0]) * fs) # Determine length of sonification num_samples = sonification_duration if sonification_duration is not None else int(np.ceil(time_coefficients[-1] * fs) + H) # Initialize sonification spectrogram_sonification = np.zeros(num_samples) for i in range(spectrogram.shape[0]): weighting_vector = np.repeat(spectrogram[i, :], H) weighting_vector = smooth_weights(weights=weighting_vector, fading_samples=int(H / 8)) sinusoid = generate_sinusoid(frequency=frequency_coefficients[i], phase=0, duration=num_samples / fs, fs=fs) spectrogram_sonification += (sinusoid * weighting_vector) spectrogram_sonification = fade_signal(spectrogram_sonification, fs=fs, fading_duration=fading_duration) spectrogram_sonification = normalize_signal(spectrogram_sonification) if normalize else spectrogram_sonification return spectrogram_sonification
[docs] def sonify_spectrogram_multi(spectrogram: np.ndarray, frequency_coefficients: np.ndarray = None, time_coefficients: np.ndarray = None, sonification_duration: int = None, fading_duration: float = 0.05, fs: int = 22050, num_processes: int = None) -> np.ndarray: """Sonifies a spectrogram using sinusoids, using multiprocessing for efficiency. Parameters ---------- sample: np.ndarray (np.float32 / np.float64) [shape=(N, K)] Spectrogram to be sonified. frequency_coefficients: np.ndarray (np.float32 / np.float64) [shape=(N, )], default = None Array containing frequency coefficients, in Hertz. time_coefficients: np.ndarray (np.float32 / np.float64) [shape=(K, )], default = None Array containing time coefficients, in seconds. 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. fs: int, default = 22050 Sampling rate, in samples per seconds. num_processes: int, default = None Number of processes Returns ------- spectrogram_sonification: np.ndarray (np.float32 / np.float64) [shape=(M, )] Sonified spectrogram. """ __check_spect_shape(spectrogram, len(frequency_coefficients), len(time_coefficients)) if num_processes is None: num_processes = os.cpu_count() or 1 H = int(np.ceil((time_coefficients[1] - time_coefficients[0]) * fs)) num_samples = sonification_duration if sonification_duration is not None else int(np.ceil(time_coefficients[-1] * fs) + H) spectrogram_sonification = np.zeros(num_samples, dtype=np.float64) num_processes = min(num_processes, spectrogram.shape[0]) with ProcessPoolExecutor(max_workers=num_processes) as executor: chunk_size = spectrogram.shape[0] // num_processes args_list = [ ( i * chunk_size, min((i + 1) * chunk_size, spectrogram.shape[0]), spectrogram[i * chunk_size: min((i + 1) * chunk_size, spectrogram.shape[0]), :], frequency_coefficients[i * chunk_size: min((i + 1) * chunk_size, spectrogram.shape[0])], time_coefficients, num_samples, H, fs ) for i in range(num_processes) ] results = list(executor.map(__sonify_chunk, args_list)) for result in results: spectrogram_sonification += result spectrogram_sonification = fade_signal(spectrogram_sonification, fs=fs, fading_duration=fading_duration) spectrogram_sonification /= np.max(spectrogram_sonification) return spectrogram_sonification
def __sonify_chunk(args): start, end, spectrogram_chunk, frequency_coefficients_chunk, time_coefficients, num_samples, H, fs = args spectrogram_sonification_chunk = np.zeros(num_samples) for i in range(spectrogram_chunk.shape[0]): weighting_vector = np.repeat(spectrogram_chunk[i, :], H) weighting_vector = smooth_weights(weights=weighting_vector, fading_samples=int(H / 8)) sinusoid = generate_sinusoid(frequency=frequency_coefficients_chunk[i], phase=0, duration=(len(weighting_vector)/fs), fading_duration=0.05, fs=fs) spectrogram_sonification_chunk += (sinusoid * weighting_vector) return spectrogram_sonification_chunk def __check_spect_shape(spect: np.ndarray, num_freq_bins: int, num_time_frames: int): # Check if lengths of coefficient vectors match shape of spectrogram if not spect.shape[0] == num_freq_bins: raise ValueError('The length of frequency_coefficients must match spectrogram.shape[0]') if not spect.shape[1] == num_time_frames: raise ValueError('The length of time_coefficients must match spectrogram.shape[1]')