Пример #1
0
def test_both_fades(function, rise_start, rise_stop, fall_start, fall_stop, sampling_rate, length):
    """Tests the behavior, if there is a fade in and a fade out."""
    # instantiate the fade
    rise_interval = sorted((rise_start, rise_stop))
    fall_interval = sorted((fall_start, fall_stop))
    fade = sumpf.Fade(function, rise_interval, fall_interval, sampling_rate, length)
    # check the metadata
    assert fade.sampling_rate() == sampling_rate
    assert fade.length() == length
    assert fade.shape() == (1, length)
    # compare the signal's channel to a simpler (and slower) implementation
    a, b = sumpf_internal.index(rise_interval, length=length)
    rise = numpy.empty(length)
    rise[0:a] = 0.0
    rise[a:b] = function(2 * (b - a))[0:b - a]
    rise[b:] = 1.0
    c, d = sumpf_internal.index(fall_interval, length=length)
    fall = numpy.empty(length)
    fall[0:c] = 1.0
    fall[c:d] = function(2 * (d - c))[d - c:]
    fall[d:] = 0.0
    if a < c or (a == b == c != d):
        window = rise * fall
        label = "Fade in-out"
    else:
        window = rise + fall
        label = "Fade out-in"
    assert not numpy.isnan(fade.channels()[0]).any()
    assert (fade.channels()[0] == window).all()
    assert fade.labels() == (label,)
Пример #2
0
    def __solve(self, start, stop):
        """A helper method, that computes a least squares solution for the ``m``
        and ``n`` parameters of the linear decay of the dB-values:

        Let ``y(i)`` be the dB-value of the energy decay curve at sample ``i``,
        then ``m`` and ``n`` are used as follows to compute the linear decay:

        .. math::

           y(i) = m \\cdot i + n


        :param start: the sample index of the first sample in the segment
        :param stop: the sample index of the first sample after the segment
        :returns: a generator, that yields (m, n) tuples (actually :func:`numpy.array` s)
                  for each channel
        """
        start_index = sumpf_internal.index(start, length=self._length)
        stop_index = sumpf_internal.index(stop, length=self._length)
        i = numpy.arange(start_index, stop_index)
        for c in self._channels:
            x = numpy.empty(shape=(len(i), 2))
            x[:, 0] = i
            x[:, 1] = 1.0
            y = 10.0 * numpy.log10(c[start_index:stop_index])
            yield numpy.linalg.lstsq(x, y, rcond=None)[0]
Пример #3
0
def test_both_fades(function, rise_start, rise_stop, fall_start, fall_stop, sampling_rate, length):
    """Tests the behavior, if there is a fade in and a fade out."""
    # instantiate the fade
    rise_interval = sorted((rise_start, rise_stop))
    fall_interval = sorted((fall_start, fall_stop))
    fade = sumpf.Fade(function, rise_interval, fall_interval, sampling_rate, length)
    # check the metadata
    assert fade.sampling_rate() == sampling_rate
    assert fade.length() == length
    assert fade.shape() == (1, length)
    # compare the signal's channel to a simpler (and slower) implementation
    a, b = sumpf_internal.index(rise_interval, length=length)
    rise = numpy.empty(length)
    rise[0:a] = 0.0
    rise[a:b] = function(2 * (b - a))[0:b - a]
    rise[b:] = 1.0
    c, d = sumpf_internal.index(fall_interval, length=length)
    fall = numpy.empty(length)
    fall[0:c] = 1.0
    fall[c:d] = function(2 * (d - c))[d - c:]
    fall[d:] = 0.0
    if a < c or (a == b == c != d):
        window = rise * fall
        label = "Fade in-out"
    else:
        window = rise + fall
        label = "Fade out-in"
    assert not numpy.isnan(fade.channels()[0]).any()
    assert (fade.channels()[0] == window).all()
    assert fade.labels() == (label,)
Пример #4
0
    def __call__(self, length):
        """Returns the index

        :param length: the length of the data set, that shall be indexed
        """
        if self.__mode is IndexMode.INTEGER:
            return sumpf_internal.index(self.__index, length)
        elif self.__mode is IndexMode.NEGATIVE_INTEGER:
            return sumpf_internal.index(self.__index, length) - length
        elif self.__mode is IndexMode.FLOAT:
            return self.__index
        elif self.__mode is IndexMode.NEGATIVE_FLOAT:
            return self.__index - 1.0
        else:
            raise ValueError(f"Unknown index mode: {self.__mode}")
Пример #5
0
 def __init__(self, plateau=0, sampling_rate=48000.0, length=8192, symmetric=True):
     """
     :param plateau: an integer length or a float factor for the signal length,
                     that specifies, how many samples in the middle of the signal
                     shall be 1.0 without being affected by the window's fade
                     in and fade out.
     :param sampling_rate: the sampling rate of the resulting signal in Hz as
                           an integer or a float
     :param length: the number of samples of the window function
     :param symmetric: True, if the window's last sample shall be the same as
                       its first sample. False, if the window's last sample shall
                       be the same as its second sample. The latter is often
                       beneficial in segmentation applications, as it makes it
                       easier to meet the "constant overlap-add"-constraint.
     """
     self.__symmetric = symmetric
     self.__plateau = sumpf_internal.index(plateau, length)
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     if self.__plateau == 0:
         if symmetric:
             channels[0, :] = self._function(length)
         else:
             channels[0, :] = self._function(length + 1)[0:-1]
     else:
         fade_length = (length - self.__plateau) // 2    # the length of the fade in or the fade out
         if symmetric:
             fade = self._function(2 * fade_length)
         else:
             fade = self._function(2 * fade_length + 1)
         channels[0, 0:fade_length] = fade[0:fade_length]
         channels[0, fade_length:length - fade_length] = 1.0
         channels[0, length - fade_length:] = fade[fade_length:2 * fade_length]
     Signal.__init__(self, channels=channels, sampling_rate=sampling_rate, offset=0, labels=(self._label(),))
Пример #6
0
def test_single_fade(function, start, stop, sampling_rate, length):
    """Tests the behavior, if there is only a fade in or out and not both."""
    # instantiate a fade in and a fade out
    interval = sorted((start, stop))
    a, b = sumpf_internal.index(interval, length=length)
    fade_in = sumpf.Fade(function=function, rise_interval=interval, sampling_rate=sampling_rate, length=length)
    fade_out = sumpf.Fade(function=function, fall_interval=interval, sampling_rate=sampling_rate, length=length)
    # instantiate test data
    rise = numpy.empty(length)
    rise[0:a] = 0.0
    rise[a:b] = function(2 * (b - a))[0:b - a]
    rise[b:] = 1.0
    fall = numpy.empty(length)
    fall[0:a] = 1.0
    fall[a:b] = function(2 * (b - a))[b - a:]
    fall[b:] = 0.0
    # check the metadata
    assert fade_in.sampling_rate() == fade_out.sampling_rate() == sampling_rate
    assert fade_in.length() == fade_out.length() == length
    assert fade_in.shape() == fade_out.shape() == (1, length)
    assert fade_in.labels() == ("Fade in",)
    assert fade_out.labels() == ("Fade out",)
    # check the samples of the fades
    assert not numpy.isnan(fade_in.channels()[0]).any()
    assert (fade_in.channels()[0] == rise).all()
    assert not numpy.isnan(fade_out.channels()[0]).any()
    assert (fade_out.channels()[0] == fall).all()
Пример #7
0
def numpy_sweep(start_frequency=20.0,
                stop_frequency=20000.0,
                phase=0.0,
                interval=(0, 1.0),
                sampling_rate=48000.0,
                length=2 ** 16):
    """A pure NumPy implementation of the ExponentialSweep for benchmarking.
    See the ExponentialSweep class for documentation of the parameters.
    """
    # allocate shared memory for the channels
    array = sharedctypes.RawArray(ctypes.c_double, length)
    channels = numpy.frombuffer(array, dtype=numpy.float64).reshape((1, length))
    # generate the sweep
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = float(start / sampling_rate)
    sweep_duration = (stop - start) / sampling_rate
    frequency_ratio = stop_frequency / start_frequency
    l = sweep_duration / math.log(frequency_ratio)
    a = 2.0 * math.pi * start_frequency * l
    t = numpy.linspace(-sweep_offset, (length - 1) / sampling_rate - sweep_offset, length)
    array = t
    array /= l
    numpy.expm1(array, out=array)
    array *= a
    array += phase
    numpy.sin(array, out=channels[0, :])
    # fake store some additional values, because these values are actually stored in the constructor of the sweep
    _ = start_frequency * frequency_ratio ** (-sweep_offset / sweep_duration)                       # noqa: F841
    _ = start_frequency * frequency_ratio ** ((sweep_duration - sweep_offset) / sweep_duration)     # noqa: F841
    return sumpf.Signal(channels=channels, sampling_rate=sampling_rate, offset=0, labels=("Sweep",))
Пример #8
0
def numpy_sweep(start_frequency=20.0,
                stop_frequency=20000.0,
                phase=0.0,
                interval=(0, 1.0),
                sampling_rate=48000.0,
                length=2 ** 16):
    """A pure NumPy implementation of the LinearSweep for benchmarking.
    See the LinearSweep class for documentation of the parameters.
    """
    # allocate shared memory for the channels
    array = sharedctypes.RawArray(ctypes.c_double, length)
    channels = numpy.frombuffer(array, dtype=numpy.float64).reshape((1, length))
    # compute some parameters
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = start / sampling_rate
    sweep_length = stop - start
    T = sweep_length / sampling_rate
    k = (stop_frequency - start_frequency) / T
    a = 2.0 * math.pi * start_frequency
    b = math.pi * k
    t = numpy.linspace(-sweep_offset, (length - 1) / sampling_rate - sweep_offset, length)  # the time values for the samples
    # generate the sweep
    array = t * t
    array *= b
    array += a * t
    array += phase
    numpy.sin(array, out=channels[0, :])
    # fake store some additional values, because these values are actually stored in the constructor of the sweep
    _ = start_frequency + k * (-sweep_offset / T)       # noqa: F841
    _ = start_frequency + k * ((T - sweep_offset) / T)  # noqa: F841
    return sumpf.Signal(channels=channels, sampling_rate=sampling_rate, offset=0, labels=("Sweep",))
Пример #9
0
def test_single_fade(function, start, stop, sampling_rate, length):
    """Tests the behavior, if there is only a fade in or out and not both."""
    # instantiate a fade in and a fade out
    interval = sorted((start, stop))
    a, b = sumpf_internal.index(interval, length=length)
    fade_in = sumpf.Fade(function=function, rise_interval=interval, sampling_rate=sampling_rate, length=length)
    fade_out = sumpf.Fade(function=function, fall_interval=interval, sampling_rate=sampling_rate, length=length)
    # instantiate test data
    rise = numpy.empty(length)
    rise[0:a] = 0.0
    rise[a:b] = function(2 * (b - a))[0:b - a]
    rise[b:] = 1.0
    fall = numpy.empty(length)
    fall[0:a] = 1.0
    fall[a:b] = function(2 * (b - a))[b - a:]
    fall[b:] = 0.0
    # check the metadata
    assert fade_in.sampling_rate() == fade_out.sampling_rate() == sampling_rate
    assert fade_in.length() == fade_out.length() == length
    assert fade_in.shape() == fade_out.shape() == (1, length)
    assert fade_in.labels() == ("Fade in",)
    assert fade_out.labels() == ("Fade out",)
    # check the samples of the fades
    assert not numpy.isnan(fade_in.channels()[0]).any()
    assert (fade_in.channels()[0] == rise).all()
    assert not numpy.isnan(fade_out.channels()[0]).any()
    assert (fade_out.channels()[0] == fall).all()
Пример #10
0
    def overlap_correlation(self, overlap):
        """Computes an estimate for the wasted computational effort in a block-wise
        processing of a signal with this window and the given overlap. The term
        overlap correlation was introduced by F. Harris in his paper *On the Use
        of Windows for Harmonic Analysis with the Discrete Fourier Transform*,
        which was published by the IEEE in January 1978.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the overlap correlation as a float, if the given overlap is a
                  scalar value, or as an array, if an array of overlaps has been
                  given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.overlap_correlation,
                                   otypes=(numpy.float64, ))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        numerator = numpy.sum(self._channels[0, 0:overlap] *
                              self._channels[0, self._length - overlap:])
        denominator = numpy.sum(numpy.square(self._channels[0]))
        if denominator == 0.0:
            return 1.0
        else:
            return numerator / denominator
Пример #11
0
    def power_flatness(self, overlap):
        """Computes the ratio of the minimum and the maximum of a squared sum of
        overlapping repetitions of this window. This is related to the power error,
        that is being made in a block-wise processing of a signal with this window.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the power flatness as a float, if the given overlap is a scalar
                  value, or as an array, if an array of overlaps has been given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.power_flatness,
                                   otypes=(numpy.float64, ))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        if overlap == self._length:
            return 1.0
        elif overlap == 0:
            squared = numpy.square(self._channels[0])
            return min(squared) / max(squared)
        else:
            step = self._length - overlap
            overlapping = numpy.empty(self._length)
            overlapping[:] = numpy.square(self._channels[0])
            for i in range(step, self._length, step):
                overlapping[0:-i] += numpy.square(self._channels[0, i:])
                overlapping[i:] += numpy.square(self._channels[0, 0:-i])
            return min(overlapping) / max(overlapping)
Пример #12
0
    def power_flatness(self, overlap):
        """Computes the ratio of the minimum and the maximum of a squared sum of
        overlapping repetitions of this window. This is related to the power error,
        that is being made in a block-wise processing of a signal with this window.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the power flatness as a float, if the given overlap is a scalar
                  value, or as an array, if an array of overlaps has been given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.power_flatness, otypes=(numpy.float64,))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        if overlap == self._length:
            return 1.0
        elif overlap == 0:
            squared = numpy.square(self._channels[0])
            return min(squared) / max(squared)
        else:
            step = self._length - overlap
            overlapping = numpy.empty(self._length)
            overlapping[:] = numpy.square(self._channels[0])
            for i in range(step, self._length, step):
                overlapping[0:-i] += numpy.square(self._channels[0, i:])
                overlapping[i:] += numpy.square(self._channels[0, 0:-i])
            return min(overlapping) / max(overlapping)
Пример #13
0
def general_sweep_parameters(interval, sampling_rate, length):
    """A helper function that computes parameters, that are used by the sweep classes."""
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = start / sampling_rate
    sweep_length = stop - start
    sweep_duration = sweep_length / sampling_rate
    t = numpy.linspace(-sweep_offset, (length - 1) / sampling_rate - sweep_offset, length)  # the time values for the samples
    return start, stop, t, sweep_duration, sweep_offset
Пример #14
0
def general_sweep_parameters(interval, sampling_rate, length):
    """A helper function that computes parameters, that are used by the sweep classes."""
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = start / sampling_rate
    sweep_length = stop - start
    sweep_duration = sweep_length / sampling_rate
    t = numpy.linspace(-sweep_offset,
                       (length - 1) / sampling_rate - sweep_offset,
                       length)  # the time values for the samples
    return start, stop, t, sweep_duration, sweep_offset
Пример #15
0
    def inverse_short_time_fourier_transform(self, window=4096, overlap=0.5, pad=True):
        """Computes a :class:`~sumpf.Signal` from this spectrogram.

        This method requires :mod:`scipy` to be installed.

        :param window: the window function, that was used to compute this spectrogram.
                       It can be passed as a :class:`~sumpf.Signal`, as an iterable
                       or an integer window length. See :func:`~sumpf._internal._functions.get_window`
                       for details.
        :param overlap: the overlap of the windowed segments. It can be passed as
                        an integer number of samples or a float fraction of the
                        window's length. Negative numbers will be added to the
                        window's length.
        :param pad: True, if the signal was padded with zeros to fit an integer
                    number of segments. False, if the samples at the end of the
                    signal, that did not fit a full segment, were ignored.
                    The results when setting this to False may be unexpected...
        """
        import scipy.signal
        # create some necessary objects
        window = sumpf_internal.get_window(window=window,
                                           overlap=overlap,
                                           symmetric=False,
                                           sampling_rate=self.__sampling_rate)
        window_length = window.length()
        overlap = sumpf_internal.index(overlap, window_length)
        step = window_length - overlap
        # compute the STFT
        if len(window) == 1:
            istft = scipy.signal.istft(self._channels,
                                       fs=self.__sampling_rate,
                                       window=window.channels()[0],
                                       nperseg=window_length,
                                       noverlap=overlap,
                                       boundary=pad)[1]
        else:
            istft = []
            for c, w in zip(self._channels, window.channels()):
                istft.append(scipy.signal.istft(c,
                                                fs=self.__sampling_rate,
                                                window=w,
                                                nperseg=window_length,
                                                noverlap=overlap,
                                                boundary=pad)[1])
        channels = sumpf_internal.allocate_array(shape=numpy.shape(istft))
        channels[:] = istft
        # return the spectrogram
        return sumpf.Signal(channels=channels,
                            sampling_rate=self.__sampling_rate * step,
                            offset=self.__offset * step,
                            labels=self._labels)
Пример #16
0
def test_min_and_max_frequencies(start_frequency, stop_frequency, sampling_rate,
                                 length, interval_start, interval_stop):
    """Tests if the methods for computing the minimum and the maximum frequencies work as expected"""
    interval = (interval_start, interval_stop)
    start, stop = sumpf_internal.index(interval, length)
    sweep_length = stop - start
    frequency_range = stop_frequency - start_frequency
    sweep = sumpf.LinearSweep(start_frequency=start_frequency,
                              stop_frequency=stop_frequency,
                              interval=interval,
                              sampling_rate=sampling_rate,
                              length=length)
    assert sweep.minimum_frequency() == pytest.approx(start_frequency + frequency_range * (-start / sweep_length))
    assert sweep.maximum_frequency() == pytest.approx(stop_frequency + frequency_range * ((length - stop) / sweep_length))  # pylint: disable=line-too-long; nothing complicated here, only long variable names
Пример #17
0
def test_min_and_max_frequencies(start_frequency, stop_frequency,
                                 sampling_rate, length, interval_start,
                                 interval_stop):
    """Tests if the methods for computing the minimum and the maximum frequencies work as expected"""
    interval = (interval_start, interval_stop)
    start, stop = sumpf_internal.index(interval, length)
    frequency_ratio = stop_frequency / start_frequency
    sweep_length = stop - start
    sweep = sumpf.ExponentialSweep(start_frequency=start_frequency,
                                   stop_frequency=stop_frequency,
                                   interval=interval,
                                   sampling_rate=sampling_rate,
                                   length=length)
    assert sweep.minimum_frequency() == pytest.approx(
        start_frequency * frequency_ratio**(-start / sweep_length))
    assert sweep.maximum_frequency() == pytest.approx(stop_frequency * frequency_ratio**((length - stop) / sweep_length))  # pylint: disable=line-too-long; nothing complicated here, only long variable names
Пример #18
0
def test_min_and_max_frequencies_inversed(start_frequency, stop_frequency, sampling_rate,
                                          length, interval_start, interval_stop):
    """Tests if the methods for computing the minimum and the maximum frequencies work as expected"""
    start, stop = sumpf_internal.index((interval_start, interval_stop), length)
    sweep = sumpf.LinearSweep(start_frequency=start_frequency,
                              stop_frequency=stop_frequency,
                              interval=(start, stop),
                              sampling_rate=sampling_rate,
                              length=length)
    isweep = sumpf.InverseLinearSweep(start_frequency=start_frequency,
                                      stop_frequency=stop_frequency,
                                      interval=(length - stop, length - start),
                                      sampling_rate=sampling_rate,
                                      length=length)
    assert isweep.minimum_frequency() == pytest.approx(sweep.minimum_frequency())
    assert isweep.maximum_frequency() == pytest.approx(sweep.maximum_frequency())
Пример #19
0
 def __init__(self,
              plateau=0,
              sampling_rate=48000.0,
              length=8192,
              symmetric=True):
     """
     :param plateau: an integer length or a float factor for the signal length,
                     that specifies, how many samples in the middle of the signal
                     shall be 1.0 without being affected by the window's fade
                     in and fade out.
     :param sampling_rate: the sampling rate of the resulting signal in Hz as
                           an integer or a float
     :param length: the number of samples of the window function
     :param symmetric: True, if the window's last sample shall be the same as
                       its first sample. False, if the window's last sample shall
                       be the same as its second sample. The latter is often
                       beneficial in segmentation applications, as it makes it
                       easier to meet the "constant overlap-add"-constraint.
     """
     self.__symmetric = symmetric
     self.__plateau = sumpf_internal.index(plateau, length)
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     if self.__plateau == 0:
         if symmetric:
             channels[0, :] = self._function(length)
         else:
             channels[0, :] = self._function(length + 1)[0:-1]
     else:
         fade_length = (length - self.__plateau
                        ) // 2  # the length of the fade in or the fade out
         if symmetric:
             fade = self._function(2 * fade_length)
         else:
             fade = self._function(2 * fade_length + 1)
         channels[0, 0:fade_length] = fade[0:fade_length]
         channels[0, fade_length:length - fade_length] = 1.0
         channels[0,
                  length - fade_length:] = fade[fade_length:2 * fade_length]
     Signal.__init__(self,
                     channels=channels,
                     sampling_rate=sampling_rate,
                     offset=0,
                     labels=(self._label(), ))
Пример #20
0
def test_min_and_max_frequencies_inversed(start_frequency, stop_frequency,
                                          sampling_rate, length,
                                          interval_start, interval_stop):
    """Tests if the methods for computing the minimum and the maximum frequencies work as expected"""
    start, stop = sumpf_internal.index((interval_start, interval_stop), length)
    sweep = sumpf.LinearSweep(start_frequency=start_frequency,
                              stop_frequency=stop_frequency,
                              interval=(start, stop),
                              sampling_rate=sampling_rate,
                              length=length)
    isweep = sumpf.InverseLinearSweep(start_frequency=start_frequency,
                                      stop_frequency=stop_frequency,
                                      interval=(length - stop, length - start),
                                      sampling_rate=sampling_rate,
                                      length=length)
    assert isweep.minimum_frequency() == pytest.approx(
        sweep.minimum_frequency())
    assert isweep.maximum_frequency() == pytest.approx(
        sweep.maximum_frequency())
Пример #21
0
    def overlap_correlation(self, overlap):
        """Computes an estimate for the wasted computational effort in a block-wise
        processing of a signal with this window and the given overlap. The term
        overlap correlation was introduced by F. Harris in his paper *On the Use
        of Windows for Harmonic Analysis with the Discrete Fourier Transform*,
        which was published by the IEEE in January 1978.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the overlap correlation as a float, if the given overlap is a
                  scalar value, or as an array, if an array of overlaps has been
                  given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.overlap_correlation, otypes=(numpy.float64,))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        numerator = numpy.sum(self._channels[0, 0:overlap] * self._channels[0, self._length - overlap:])
        denominator = numpy.sum(numpy.square(self._channels[0]))
        if denominator == 0.0:
            return 1.0
        else:
            return numerator / denominator
Пример #22
0
    def scaling_factor(self, overlap):
        """Computes a correction factor for block-wise processed signals, that are
        split and weighted with this window, that maintains the original signal's
        amplitude in the processed signal.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the scaling factor as a float, if the given overlap is a scalar
                  value, or as an array, if an array of overlaps has been given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.scaling_factor,
                                   otypes=(numpy.float64, ))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        step = self._length - overlap
        if step == 0:
            return 0.0
        added = numpy.sum(self._channels[0])
        for shift in range(step, self._length, step):
            added += numpy.sum(self._channels[0, 0:self._length - shift])
            added += numpy.sum(self._channels[0, shift:])
        return self._length / added
Пример #23
0
    def scaling_factor(self, overlap):
        """Computes a correction factor for block-wise processed signals, that are
        split and weighted with this window, that maintains the original signal's
        amplitude in the processed signal.

        :param overlap: the overlap of the weighted segments as an integer of samples,
                        a float factor of the window's length or as an array to
                        compute the scaling factor for multiple overlaps at once.
        :returns: the scaling factor as a float, if the given overlap is a scalar
                  value, or as an array, if an array of overlaps has been given.
        """
        if isinstance(overlap, collections.abc.Iterable):
            return numpy.vectorize(self.scaling_factor, otypes=(numpy.float64,))(overlap)
        overlap = sumpf_internal.index(overlap, self._length)
        step = self._length - overlap
        if step == 0:
            return 0.0
        added = 0.0
        for shift in range(0, self._length, step):
            added += numpy.sum(self._channels[0, 0:self._length - shift])
            if shift != 0:
                added += numpy.sum(self._channels[0, shift:])
        return self._length / added
Пример #24
0
def numpy_sweep(start_frequency=20.0,
                stop_frequency=20000.0,
                phase=0.0,
                interval=(0, 1.0),
                sampling_rate=48000.0,
                length=2**16):
    """A pure NumPy implementation of the ExponentialSweep for benchmarking.
    See the ExponentialSweep class for documentation of the parameters.
    """
    # allocate shared memory for the channels
    array = sharedctypes.RawArray(ctypes.c_double, length)
    channels = numpy.frombuffer(array, dtype=numpy.float64).reshape(
        (1, length))
    # generate the sweep
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = float(start / sampling_rate)
    sweep_duration = (stop - start) / sampling_rate
    frequency_ratio = stop_frequency / start_frequency
    l = sweep_duration / math.log(frequency_ratio)
    a = 2.0 * math.pi * start_frequency * l
    t = numpy.linspace(-sweep_offset,
                       (length - 1) / sampling_rate - sweep_offset, length)
    array = t
    array /= l
    numpy.expm1(array, out=array)
    array *= a
    array += phase
    numpy.sin(array, out=channels[0, :])
    # fake store some additional values, because these values are actually stored in the constructor of the sweep
    _ = start_frequency * frequency_ratio**(-sweep_offset / sweep_duration
                                            )  # noqa: F841
    _ = start_frequency * frequency_ratio**(
        (sweep_duration - sweep_offset) / sweep_duration)  # noqa: F841
    return sumpf.Signal(channels=channels,
                        sampling_rate=sampling_rate,
                        offset=0,
                        labels=("Sweep", ))
Пример #25
0
def numpy_sweep(start_frequency=20.0,
                stop_frequency=20000.0,
                phase=0.0,
                interval=(0, 1.0),
                sampling_rate=48000.0,
                length=2**16):
    """A pure NumPy implementation of the LinearSweep for benchmarking.
    See the LinearSweep class for documentation of the parameters.
    """
    # allocate shared memory for the channels
    array = sharedctypes.RawArray(ctypes.c_double, length)
    channels = numpy.frombuffer(array, dtype=numpy.float64).reshape(
        (1, length))
    # compute some parameters
    start, stop = sumpf_internal.index(interval, length)
    sweep_offset = start / sampling_rate
    sweep_length = stop - start
    T = sweep_length / sampling_rate
    k = (stop_frequency - start_frequency) / T
    a = 2.0 * math.pi * start_frequency
    b = math.pi * k
    t = numpy.linspace(-sweep_offset,
                       (length - 1) / sampling_rate - sweep_offset,
                       length)  # the time values for the samples
    # generate the sweep
    array = t * t
    array *= b
    array += a * t
    array += phase
    numpy.sin(array, out=channels[0, :])
    # fake store some additional values, because these values are actually stored in the constructor of the sweep
    _ = start_frequency + k * (-sweep_offset / T)  # noqa: F841
    _ = start_frequency + k * ((T - sweep_offset) / T)  # noqa: F841
    return sumpf.Signal(channels=channels,
                        sampling_rate=sampling_rate,
                        offset=0,
                        labels=("Sweep", ))
Пример #26
0
    def short_time_fourier_transform(self, window=4096, overlap=0.5, pad=True):
        """Computes a :class:`~sumpf.Spectrogram` from this signal.

        This method requires :mod:`scipy` to be installed.

        The spectrogram's offset is rounded to the nearest integer, which corresponds
        to the scaled offset of this signal. The remainder is then taken into account
        as a constant addition to the group delay.

        :param window: the window function, that is used to segment this signal.
                       It can be passed as a :class:`~sumpf.Signal`, as an iterable
                       or an integer window length. See :func:`~sumpf._internal._functions.get_window`
                       for details.
        :param overlap: the overlap of the signal segments. It can be passed as
                        an integer number of samples or a float fraction of the
                        window's length. Negative numbers will be added to the
                        window's length.
        :param pad: True, if the signal shall be padded with zeros to fit an integer
                    number of segments. False, if the samples at the end of the
                    signal, that do not fit a full segment, shall be ignored.
        """
        import scipy.signal
        # create some necessary objects
        window = sumpf_internal.get_window(window=window,
                                           overlap=overlap,
                                           symmetric=False,
                                           sampling_rate=self.__sampling_rate)
        window_length = window.length()
        overlap = sumpf_internal.index(overlap, window_length)
        step = window_length - overlap
        # compute the STFT
        if len(window) == 1:
            stft = scipy.signal.stft(self._channels,
                                     fs=self.__sampling_rate,
                                     window=window.channels()[0],
                                     nperseg=window_length,
                                     noverlap=overlap,
                                     boundary="zeros" if pad else None,
                                     padded=pad)[2]
        else:
            stft = []
            for c, w in zip(self._channels, window.channels()):
                stft.append(scipy.signal.stft(c,
                                              fs=self.__sampling_rate,
                                              window=w,
                                              nperseg=window_length,
                                              noverlap=overlap,
                                              boundary="zeros" if pad else None,
                                              padded=pad)[2])
        channels = sumpf_internal.allocate_array(shape=numpy.shape(stft), dtype=numpy.complex128)
        channels[:] = stft
        # deal with the offset
        resolution = self.__sampling_rate / window_length
        offset = int(round(self.__offset / step))
        offset_remainder = self.__offset - (offset * step)
        if offset_remainder:
            max_frequency = (window_length - 1) * resolution
            compensation = numpy.linspace(0.0, max_frequency, window_length // 2 + 1, dtype=numpy.complex128)
            compensation *= -1j * 2.0 * math.pi * (offset_remainder / self.__sampling_rate)
            numpy.exp(compensation, out=compensation)
            for c in channels:
                t = c.transpose()
                t *= compensation
        # return the spectrogram
        return sumpf.Spectrogram(channels=channels,
                                 resolution=resolution,
                                 sampling_rate=self.__sampling_rate / step,
                                 offset=offset,
                                 labels=self._labels)
Пример #27
0
 def __init__(self,
              function=numpy.blackman,
              rise_interval=None,
              fall_interval=None,
              sampling_rate=48000.0,
              length=2**16):
     """
     :param function: a function, that takes an integer length and returns the
                      samples of a fade in, followed by a fade out, which have
                      in total as many samples as the given length. :mod:`numpy`'s
                      window functions can be used for this parameter.
     :param rise_interval: a tuple of sample indices between which the fade in
                           shall be done. The sample indices can be given as
                           integers or as floats between 0.0 and 1.0, that are
                           multiplied with the given length of the signal.
                           Negative numbers will be counted from the back of
                           the signal. Combining a float with an integer is
                           possible.
     :param fall_interval: a tuple of sample indices between which the fade out
                           shall be done. The sample indices can be given as
                           integers or as floats between 0.0 and 1.0, that are
                           multiplied with the given length of the signal.
                           Negative numbers will be counted from the back of
                           the signal. Combining a float with an integer is
                           possible.
     :param sampling_rate: the sampling rate of the signal as a float or integer
     :param length: the number of samples of the fade signal
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     channel = channels[0]
     # parse the raise and fall intervals
     if rise_interval is None:
         rise = None
     elif isinstance(rise_interval, collections.abc.Iterable):
         rise = sumpf_internal.index(rise_interval, length=length)
     else:
         rise = (0, sumpf_internal.index(rise_interval, length=length))
     if fall_interval is None:
         fall = None
     elif isinstance(fall_interval, collections.abc.Iterable):
         fall = sumpf_internal.index(fall_interval, length=length)
     else:
         fall = (sumpf_internal.index(fall_interval, length=length), length)
     # initialize the channels
     if rise is None:  # only fade out
         if fall is None:
             channel[:] = 1.0
             label = "Fade"
         else:
             a, b = fall
             fall_length = b - a
             channel[0:a] = 1.0
             channel[a:b] = function(2 * fall_length)[fall_length:]
             channel[b:] = 0.0
             label = "Fade out"
     elif fall is None:  # only fade in
         a, b = rise
         rise_length = b - a
         channel[0:a] = 0.0
         channel[a:b] = function(2 * rise_length)[0:rise_length]
         channel[b:] = 1.0
         label = "Fade in"
     elif (rise[0] < fall[0]) or ((rise[0] == rise[1] == fall[0] !=
                                   fall[1])):  # fade in and then out
         a, b = rise
         c, d = fall
         start, stop = min(a, c), max(b, d)
         channel[0:start] = 0.0
         fade_in_out(function,
                     rise_stop=b - start,
                     fall_start=c - start,
                     fall_length=d - c,
                     out=channel[start:stop])
         channel[stop:] = 0.0
         label = "Fade in-out"
     else:  # fade out and then in
         a, b = fall
         c, d = rise
         start, stop = min(a, c), max(b, d)
         channel[0:start] = 1.0
         fade_out_in(function,
                     fall_stop=b - a,
                     rise_start=c - a,
                     rise_length=d - c,
                     out=channel[min(a, c):max(b, d)])
         channel[stop:] = 1.0
         label = "Fade out-in"
     # initialize the signal
     Signal.__init__(self,
                     channels=channels,
                     sampling_rate=sampling_rate,
                     offset=0,
                     labels=(label, ))