def playaudio(samples, sampling_freq, wait_to_finish=True): """ Plays audio using pygame Args: samples: Audio samples sampling_freq: Sampling frequency wait_to_finish: Wait for the player to finish before returning Returns: The samples passed to the player """ try: import pygame as pyg except ImportError: raise pyg.mixer.init(frequency=sampling_freq, channels=2) samples = np.array(samples, dtype='int16') if samples.ndim == 1: samples = np.concatenate((column_vector(samples), column_vector(samples)), axis=1) elif samples.ndim > 2: samples = samples[:, 0:2] pyg.mixer.Sound(array=samples).play() if wait_to_finish: nsecs = int(np.ceil(samples.shape[0] / float(sampling_freq))) print(getfname() + " : Playing " + repr(nsecs) + " seconds of audio") while pyg.mixer.get_busy(): pass return samples
def overlapadd(input_frames, window_samples=None, inc=None, previous_partial_output=None): """ Performs overlap-add using the frames in input_samples. This is a Python implementation of the MATLAB code available in the VOICEBOX toolbox. (http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/doc/voicebox/overlapadd.html) Args: input_frames: The array of input frames of size M X window_samples window_samples: The window to be used for the frames inc: The increment in samples between frames previous_partial_output: Provide the partial output returned from a previous call to this function Returns: The overlap-add result and the partial output at the end """ if window_samples is None: window_samples = np.ones((input_frames.shape[1], )) elif window_samples.size != input_frames.shape[1]: raise NameError(getfname() + ":WindowSizeDoesNotMatchFrameSize") if inc is None: inc = input_frames.shape[1] elif inc > input_frames.shape[1]: raise NameError(getfname() + ":SampleIncrementTooLarge") nr = input_frames.shape[0] nf = input_frames.shape[1] nb = int(np.ceil(nf / float(inc))) no = int(nf + (nr - 1) * inc) overlapped_output_shape = (no, nb) z = np.zeros((int(no * nb), )) # input_frames = np.asfortranarray(input_frames) zidx = (np.repeat(row_vector(np.arange(0, nf, dtype=np.int)), nr, axis=0) + np.repeat(column_vector( np.arange(0, nr, dtype=np.int) * inc + (np.arange(0, nr, dtype=np.int) % nb) * no), nf, axis=1)) # input_frames_windowed = input_frames * np.repeat(row_vector(window_samples), n_frames, axis=0) input_frames *= np.repeat(row_vector(window_samples), nr, axis=0) z[zidx.flatten(order='F').astype( np.int32)] = input_frames.flatten(order='F') z = z.reshape(overlapped_output_shape, order='F') if z.ndim > 1: z = np.sum(z, axis=1) if previous_partial_output is not None: if previous_partial_output.ndim > 1: raise NameError(getfname() + "PrevPartialOutDimError") else: z[0:previous_partial_output.size] += previous_partial_output out_samples = int(inc * nr) if no < out_samples: z[out_samples] = 0 current_partial_output = np.array([]) else: current_partial_output = z[out_samples:] z = z[0:out_samples] return z, current_partial_output
def gm_frac_delayed_copies(amplitudes, delays, tot_length, excitation_signal=np.array([]), center_filter_peaks=True): """ The function when called implements the model defined as: (1) h(n) = \sum_{i=1}^{D}\left[{\beta_i}h_e(n){\ast}\frac{\sin~\pi(n-k_i)}{\pi(n-k_i)}\right] This is the model proposed in [1] This is effectively the summation on D copies of the signal excitation_signal. This copies are placed at sample locations 'delays' (which are not bound to integers) and their scaling is defined by 'amplitudes'. The length of the signal is defined as 'tot_length'. Args: amplitudes: Vector containing the scaling of each copy delays: Sample index of occurence each copy tot_length: Total length of the output vector excitation_signal: The signal to be copied at each location center_filter_peaks: Center the signal in 'excitation_signal', so that the samples of maximum energy occur at samples 'delays' Returns: y : h(n) from the equation (1) Y : A matrix containing as vectors the components of the summation in equation (1) [1] Papayiannis, C., Evers, C. and Naylor, P.A., 2017, August. Sparse parametric modeling of the early part of acoustic impulse responses. In Signal Processing Conference (EUSIPCO), 2017 25th European (pp. 678-682). IEEE. """ excitation_signal = np.atleast_2d(excitation_signal) if excitation_signal.shape[1] == 1: excitation_signal = excitation_signal.T if excitation_signal.size > 0: nfilters = excitation_signal.shape[0] else: nfilters = 0 sincspan = 9 amplitudes = np.atleast_2d(amplitudes) if amplitudes.shape[1] == 1: amplitudes = amplitudes.T delays = np.atleast_2d(delays) if delays.shape[1] == 1: delays = delays.T tot_components = amplitudes.size if delays.size != tot_components: raise NameError(getfname() + ':InputsMissmatch tot components are ' + str(tot_components) + ' and got delays ' + str(delays.size)) if nfilters > 1: raise NameError(getfname() + ':InputsMissmatch') if tot_components < 1: yindiv = np.array([]) ysignal = np.zeros((tot_length, )) return [ysignal, yindiv] sample_indices = np.repeat(column_vector( np.arange(0, tot_length, 1, dtype=np.float64)), tot_components, axis=1) sample_indices_offsetting = np.repeat(row_vector(delays), tot_length, axis=0) sample_indices -= sample_indices_offsetting yindiv = np.sinc(sample_indices) * np.repeat( row_vector(amplitudes), tot_length, axis=0) if ~np.isinf(sincspan): spanscale_numer = sincspan * np.sin( np.pi * sample_indices / float(sincspan)) spanscale_denom = (np.pi * sample_indices) # Lanczos kernel limiting_case_idx = np.where(spanscale_denom == 0) spanscale_denom[limiting_case_idx] = 1 spanscale = spanscale_numer / spanscale_denom spanscale[limiting_case_idx] = 1 yindiv *= spanscale yindiv[np.where(abs(sample_indices > sincspan))] = 0 excitation_signal = excitation_signal.flatten() if nfilters > 0: if not center_filter_peaks: yindiv = lfilter(excitation_signal, [1], yindiv, axis=0) else: fcenter = np.argmax(abs(excitation_signal), axis=0) if fcenter == 0: yindiv = lfilter(excitation_signal, [1], yindiv, axis=0) else: futuresamples = fcenter tmpconc = np.concatenate( (yindiv, np.zeros((futuresamples, yindiv.shape[1]))), axis=0) tmpconc = lfilter(excitation_signal, [1], tmpconc, axis=0) yindiv = tmpconc[futuresamples:, :] ysignal = np.sum(yindiv, axis=1) return [ysignal, yindiv]
def get_t60_decaymodel(air_fir_taps, sampling_freq): """ Estimates the Reverberation Time, given an Acoustic Impulse Response. The mode of operation is defined by the reference below. This is a wrapper of a python translation of the code made available by the authors of: Karjalainen, Antsalo, and Peltonen, Estimation of Modal Decay Parameters from Noisy Response Measurements. at : http://www.acoustics.hut.fi/software/decay Examples: When involving an AIR that is sampled at 48 kHz for example. reverb_time = get_t60_decaymodel(air, 48000) Args: air_fir_taps : Acoustic Impulse Response to process sampling_freq : The sampling frequency at which air was recorded at Returns: An estimate of the reverbration time in seconds """ def decay_model(x_points, param0, param1, param2): """ The function used bo the non-linear least squares fitting method to estimate the decay parameters""" expf = 0.4 y1_dm = np.multiply(param0, np.exp(np.multiply(param1, x_points))) y2_dm = param2 fit_res = np.multiply( weights, np.power(np.add(np.power(y1_dm, 2), np.power(y2_dm, 2)), 0.5 * expf)) return fit_res # air is a 1D list # Set up things. Move to dB domain and scale leny = len(air_fir_taps) air = np.multiply(20, np.log10(abs(air_fir_taps) + np.finfo(float).eps)) _, ymaxi = matmax(air) air = air - air[ymaxi] weights = [1] * leny weights[0:max(1, ymaxi)] = [0] * max(1, ymaxi) time_points = np.linspace(0, leny / float(sampling_freq), leny) # Lin fit leny2 = leny // 2 leny10 = leny // 10 ydata = np.power(np.power(10, air / 20.), 0.4) start_of_range = np.nonzero(weights)[0][0] meanval1 = np.mean(ydata[start_of_range:leny10 + start_of_range + 1]) meanvaln = np.mean(ydata[leny - leny10:leny]) tmat = np.concatenate( (np.ones((leny2, 1)), column_vector(time_points[start_of_range:leny2 + start_of_range])), axis=1) tau0 = np.linalg.lstsq(tmat, air[start_of_range:leny2 + start_of_range], rcond=None) tau0 = tau0[0][1] / 8.7 ydata = np.multiply(weights, np.array(ydata)) fit_bounds = ([0, -2000, 0], [200., -0.1, 200.]) if tau0 > -0.1: # to satisfy the bounds tau0 = -0.1 sol_final = curve_fit(decay_model, time_points, ydata, p0=(meanval1, tau0, meanvaln), bounds=fit_bounds) reverb_time = np.log(1 / 1000.) / float(sol_final[0][1]) if reverb_time <= 0: raise NameError(getfname() + ':NegativeRT') return reverb_time
def __init__(self, name='', filename='', samples=None, sampling_freq=0, keep_channel=None, max_allowed_silence=0.001, is_simulation=None, silent_samples_offset=False, matlab_engine=None): """ Args: name: Used as the label for the object filename: The filename for the measured/simualted AIR samples: The samples of the measured/simulated AIR sampling_freq (int): The sampling frequency for the AIR """ self.name = name self.sampling_freq = 0 self.impulse_response = np.array([]) self.room_name = None self.room_type = None self.room_dimensions = (None, None, None) self.rec_position = [(None, None, None)] self.src_position = (None, None, None) self.is_simulation = is_simulation self.from_database = None self.receiver_name = None self.receiver_config = None self.known_room = False self.filename = filename self.nchannels = 0 max_allowed_silence_samples = int( np.ceil(max_allowed_silence * self.sampling_freq)) if (len(filename) == 0) & (samples is None): raise NameError(getfname() + ':FilenameOrSamplesRequired') if samples is not None: self.impulse_response = samples if sampling_freq <= 0: raise AssertionError('SamplingFreqCannotBe0orNegative') self.sampling_freq = sampling_freq if keep_channel is not None: self.impulse_response = self.get_channel(keep_channel) else: self.sampling_freq, self.impulse_response = wavfile.read( self.filename) if keep_channel is not None: self.impulse_response = self.get_channel(keep_channel) self.impulse_response = self.impulse_response.astype( float) / np.max(np.abs(self.impulse_response)) if sampling_freq > 0: self.impulse_response = np.array( my_resample(np.array(self.impulse_response), self.sampling_freq, sampling_freq, matlab_eng=matlab_engine)) self.sampling_freq = sampling_freq try: if self.impulse_response.ndim == 1: self.impulse_response = column_vector(self.impulse_response) except AttributeError: pass if len(filename) > 0: self.add_room_info() self.add_receiver_info() if self.impulse_response.ndim < 2: self.nchannels = 1 else: self.nchannels = self.impulse_response.shape[1] scale_by = float(abs(self.impulse_response).max()) if scale_by > 0: self.impulse_response = self.impulse_response / scale_by if self.impulse_response is not None: start_sample = self.impulse_response.shape[0] for i in range(self.impulse_response.shape[1]): start_sample = min( start_sample, max( 0, np.nonzero(self.impulse_response[:, i])[0][0] - max_allowed_silence_samples)) if start_sample > 0 and silent_samples_offset: self.impulse_response = self.impulse_response[start_sample:, :] print('Offsetted AIR by ' + str(start_sample) + ' samples')