Beispiel #1
0
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
Beispiel #2
0
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:])
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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')