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 enframe(alike, flength, fincr, hamming_window=False): """ Breaks the input into frames of length flength, with an increment of fincr samples per frame Args: alike: Input array like description of a vector flength: Frame Length in samples fincr: Frame Increment in Samples hamming_window: Apply hamming window on frames Returns: The signal broken into frames """ npa = np.array(alike) flength = int(flength) fincr = int(fincr) if npa.ndim > 1: raise NameError(getfname() + ':Non1DInput') if not (flength % fincr) == 0: raise NameError(getfname() + ':SizeOfFrameNotMultipleOfIncrement') nshifts = int(flength / float(fincr)) totnframes = int(np.ceil(npa.size / float(fincr))) noframes_per_shift = int(np.ceil(totnframes / float(nshifts))) discardlastframes = (totnframes % nshifts) > 0 xout = np.zeros((noframes_per_shift * nshifts, flength)) tnsamples = int(noframes_per_shift * flength) fidxs = np.arange(0, noframes_per_shift, dtype=np.int) * nshifts npadding = (flength - npa.size % flength) % flength + flength npa = np.append(npa, np.zeros(npadding)) for i in range(nshifts): fidxs += i xout[fidxs, :] = np.array(npa[i * fincr:i * fincr + tnsamples]).reshape(fidxs.size, flength) if hamming_window: xout[fidxs, :] = xout[fidxs, :] * np.hamming(flength) if discardlastframes: xout = xout[0:-1, :] return xout
def get_drr_linscale(air_fir_taps, sampling_freq, direct_window_length_secs=0.0008, ignore_reflections_up_to=None): """ Estimates the Direct to Reverberant ration given an Acoustic Impulse Response (AIR) Args: air_fir_taps: The taps of the AIR sampling_freq: The sampling frequency direct_window_length_secs: The length of the window that is estimated ot contain the direct sound ignore_reflections_up_to: The reflections up to this point (in seconds) are ignored and not considered to be part of either the early or the late part. Returns: The DRR in linear scale """ air_fir_taps = np.array(air_fir_taps) if air_fir_taps.ndim > 1: raise NameError(getfname() + "AIR_Not1D") nsidesamples = int(np.ceil(direct_window_length_secs / 2. * sampling_freq)) dpathcenter = abs(air_fir_taps).argmax() dstartsample = max(dpathcenter, dpathcenter - nsidesamples) dendsample = min(dpathcenter + nsidesamples, air_fir_taps.size) if ignore_reflections_up_to is not None: ignore_until_sample = int( np.ceil(ignore_reflections_up_to * sampling_freq)) if ignore_until_sample > dendsample: air_fir_taps[dendsample:ignore_until_sample] = 0 else: print( 'You gave an ignore range for DRR calculation but it was invalid' ) return get_array_energy(air_fir_taps[dstartsample:dendsample]) \ / get_array_energy(air_fir_taps[dendsample:])
def plotnorm(x=None, y=None, title=None, interactive=False, clf=False, savelocation=None, no_rescaling=False, **mplotargs): """ A useful and flexible plotting tool for signal processing. It allows you to plot a number of signals on the same normalised scale. It can plot signals in the time domain when provided with a sampling frequency. Args: x: The x axis points or the sampling frequency as a scalar y: The list of vectros or the array to plot. the vectors can have a different number of elements each title: The string to use as the plot title interactive: Wait for the user to close the plot before continuing clf: Clear the plot before plotting savelocation: Save hte plot as this file no_rescaling: Do not normalize the scale of the signals **mplotargs: Arguments to be passed to matplotlib.pyplot.plot Returns: The plot """ hasfs = False if (x is None) & (y is not None): x = np.arange(y.size) elif (not isinstance(x, np.ndarray)) & (y is not None): sampling_freq = float(x) x = np.arange(y.size) / sampling_freq hasfs = True elif x.size != y.size: raise NameError(getfname() + 'XYSizeMismatch') elif x.size == 0: return if not no_rescaling: y = y / abs(y).max() if clf: mplot.clf() res = mplot.plot(x, y, linewidth=0.5, **mplotargs) gen_title = 'Normalised Amplitude' if hasfs: gen_title += ' at Fs=' + repr(sampling_freq) + 'Hz' mplot.xlabel('Time (s)') else: mplot.xlabel('Sample') if title is None: mplot.title(gen_title) elif not title == '': mplot.title(title) mplot.ylabel('Normalised Amplitude') mplot.grid(True) if savelocation is not None: mplot.savefig(savelocation) print('Saved: ' + savelocation) if interactive: mplot.show() return res
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')