Example #1
0
    def shift(self, shift, mode=sumpf_internal.ShiftMode.OFFSET):
        """Returns a spectrogram, which is shifted in time.

        Positive shifts result in a delayed spectrogram, while negative shifts prepone it.
        In mode ``OFFSET``, it is allowed to pass ``None`` for the ``shift`` parameter,
        which sets the offset of the resulting :class:`~sumpf.Spectrogram` to 0.

        The shift can be performed in different ways, which can be specified with
        the ``mode`` parameter. The flags, that can be passed to this parameter
        are defined and documented in the :class:`sumpf.Spectrogram.shift_modes`
        enumeration.

        :param shift: an integer number of samples, by which the spectrogram shall be shifted
        :param mode: a flag from the :class:`sumpf.Spectrogram.shift_modes` enumeration
        :returns: the shifted :class:`~sumpf.Spectrogram`
        """
        if shift == 0:
            return self
        elif mode == Spectrogram.shift_modes.OFFSET:
            if shift is None:
                return Spectrogram(channels=self._channels,
                                   resolution=self.__resolution,
                                   sampling_rate=self.__sampling_rate,
                                   offset=0,
                                   labels=self._labels)
            else:
                return Spectrogram(channels=self._channels,
                                   resolution=self.__resolution,
                                   sampling_rate=self.__sampling_rate,
                                   offset=self.__offset + shift,
                                   labels=self._labels)
        else:
            if mode == Spectrogram.shift_modes.CROP:
                channels = sumpf_internal.allocate_array(shape=self._channels.shape, dtype=numpy.complex128)
                if shift < 0:
                    channels[:, :, 0:shift] = self._channels[:, :, -shift:]
                    channels[:, :, shift:] = 0.0
                else:
                    channels[:, :, 0:shift] = 0.0
                    channels[:, :, shift:] = self._channels[:, :, 0:-shift]
            elif mode == Spectrogram.shift_modes.PAD:
                channels = sumpf_internal.allocate_array(shape=(len(self),
                                                                self.__frequencies,
                                                                self._length + abs(shift)),
                                                         dtype=numpy.complex128)
                if shift < 0:
                    channels[:, :, 0:self._length] = self._channels
                    channels[:, :, self._length:] = 0.0
                else:
                    channels[:, :, 0:shift] = 0.0
                    channels[:, :, shift:] = self._channels
            elif mode == Spectrogram.shift_modes.CYCLE:
                channels = sumpf_internal.allocate_array(shape=self._channels.shape, dtype=numpy.complex128)
                channels[:, :, 0:shift] = self._channels[:, :, -shift:]
                channels[:, :, shift:] = self._channels[:, :, 0:-shift]
            return Spectrogram(channels=channels,
                               resolution=self.__resolution,
                               sampling_rate=self.__sampling_rate,
                               offset=self.__offset,
                               labels=self._labels)
Example #2
0
    def _algebra_function(self, other, function, other_pivot, label):
        """Protected helper function that implements the broadcasting of spectrums
        or arrays with different shapes when using the overloaded math operators.

        :param other: the object "on the other side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :param other_pivot: a default value, that is used as first operand for samples, where
                            only the other object has data (e.g. when the two spectrums don't
                            overlap due to different lengths). If ``other_pivot`` is ``None``,
                            the data from the other object is copied.
        :param label: the string label for the computed channels
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        if isinstance(other, Spectrum):
            if len(self) == 1:
                channels = sumpf_internal.allocate_array(
                    shape=(len(other), self._length), dtype=numpy.complex128)
                function(self._channels[0], other.channels(), out=channels)
            elif len(other) == 1:
                channels = sumpf_internal.allocate_array(
                    shape=self.shape(), dtype=numpy.complex128)
                function(self._channels, other.channels()[0], out=channels)
            elif len(self) < len(other):
                channels = sumpf_internal.allocate_array(
                    shape=(len(other), self._length), dtype=numpy.complex128)
                function(self._channels,
                         other.channels()[0:len(self)],
                         out=channels[0:len(self)])
                if other_pivot is None:
                    channels[len(self):] = other.channels()[len(self):]
                else:
                    function(other_pivot,
                             other.channels()[len(self):],
                             out=channels[len(self):])
            else:
                channels = sumpf_internal.allocate_array(
                    shape=self.shape(), dtype=numpy.complex128)
                function(self._channels[0:len(other)],
                         other.channels(),
                         out=channels[0:len(other)])
                channels[len(other):] = self._channels[len(other):]
            return Spectrum(channels=channels,
                            resolution=self.__resolution,
                            labels=(label, ) * len(channels))
        elif isinstance(other, (SampledData, sumpf.Filter)):
            return NotImplemented
        else:  # other is an array or a number
            try:
                return Spectrum(channels=function(
                    self._channels,
                    other,
                    out=sumpf_internal.allocate_array(self.shape(),
                                                      numpy.complex128)),
                                resolution=self.__resolution,
                                labels=self._labels)
            except TypeError:
                return NotImplemented
Example #3
0
 def __init__(self,
              bits=None,
              seed=None,
              sampling_rate=48000.0,
              length=None):
     """
     :param bits: the integer size of the shift register, with which the MLS is generated
     :param seed: a seed for the random initialization of the shift register
     :param sampling_rate: the sampling rate of the MLS as a float
     :param length: the length of the signal
     """
     if length is None:
         if bits is None:
             bits = 16
         length = 2**bits - 1
     elif bits is None:
         bits = int(math.ceil(math.log2(length + 1)))
     random = numpy.random.default_rng(seed)
     state = random.choice((False, True), size=bits)
     if True not in state:
         state[random.integers(low=0, high=len(state) - 1)] = True
     sequence = scipy.signal.max_len_seq(bits, state, length)[0]
     channels = sumpf_internal.allocate_array(shape=(1, len(sequence)))
     channels[0, :] = sequence
     channels *= 2.0
     channels -= 1.0
     Signal.__init__(self,
                     channels=channels,
                     sampling_rate=sampling_rate,
                     offset=0,
                     labels=("MLS", ))
     self.__bits = bits
Example #4
0
 def __init__(self,
              frequency=1000.0,
              phase=0.0,
              sampling_rate=48000.0,
              length=2**16):
     """
     :param frequency: the square wave's frequency in Hz
     :param phase: a phase offset in radians
     :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 sine wave
     """
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     duration = (length - 1) / sampling_rate
     omega_t_plus_phase = numpy.linspace(
         phase, 2.0 * math.pi * frequency * duration + phase, length)
     numpy.sin(omega_t_plus_phase, out=channels[0])
     numpy.sign(channels, out=channels)
     Wave.__init__(self,
                   frequency=frequency,
                   channels=channels,
                   sampling_rate=sampling_rate,
                   offset=0,
                   labels=("Square wave", ))
Example #5
0
    def output(self):
        """Computes the merged spectrum and returns it.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        if len(self.__spectrums) == 0:            # pylint: disable=len-as-condition
            return sumpf.Spectrum()
        elif len(self.__spectrums) == 1:
            return next(iter(self.__spectrums.values()))    # simply return the first and only spectrum
        else:
            # find the number of channels and the merged spectrum's length
            number_of_channels = sum(len(s) for s in self.__spectrums.values())
            length = max(s.length() for s in self.__spectrums.values())
            channels = sumpf_internal.allocate_array(shape=(number_of_channels, length), dtype=numpy.complex128)
            labels = [""] * number_of_channels
            # fill in the data
            if self.__mode == MergeSpectrums.modes.FIRST_DATASET_FIRST:
                self.__first_dataset_first(channels, length, labels)
            elif self.__mode == MergeSpectrums.modes.FIRST_CHANNELS_FIRST:
                self.__first_channels_first(channels, labels)
            else:
                raise ValueError("invalid mode: {}".format(self.__mode))
            # create and return the result
            return sumpf.Spectrum(channels=channels,
                                  resolution=next(iter(self.__spectrums.values())).resolution(),
                                  labels=labels)
Example #6
0
    def output(self):
        """Computes the merged signal and returns it.

        :returns: a :class:`~sumpf.Signal` instance
        """
        if len(self.__signals) == 0:            # pylint: disable=len-as-condition
            return sumpf.Signal()
        elif len(self.__signals) == 1:
            return next(iter(self.__signals.values()))  # simply return the first and only signal
        else:
            # find the offset, the number of channels and the merged signal's length
            offset = min(s.offset() for s in self.__signals.values())
            number_of_channels = sum(len(s) for s in self.__signals.values())
            length = max(s.offset() + s.length() for s in self.__signals.values()) - offset
            channels = sumpf_internal.allocate_array(shape=(number_of_channels, length), dtype=numpy.float64)
            labels = [""] * number_of_channels
            # fill in the data
            if self.__mode == MergeSignals.modes.FIRST_DATASET_FIRST:
                self.__first_dataset_first(channels, length, offset, labels)
            elif self.__mode == MergeSignals.modes.FIRST_CHANNELS_FIRST:
                self.__first_channels_first(channels, offset, labels)
            else:
                raise ValueError("invalid mode: {}".format(self.__mode))
            # create and return the result
            return sumpf.Signal(channels=channels,
                                sampling_rate=next(iter(self.__signals.values())).sampling_rate(),
                                offset=offset,
                                labels=labels)
Example #7
0
 def __init__(self, bits=None, seed=None, sampling_rate=48000.0, length=None):
     """
     :param bits: the integer size of the shift register, with which the MLS is generated
     :param seed: a seed for the random initialization of the shift register
     :param sampling_rate: the sampling rate of the MLS as a float
     :param length: the length of the signal
     """
     if length is None:
         if bits is None:
             bits = 16
         length = 2 ** bits - 1
     elif bits is None:
         bits = int(math.ceil(math.log2(length + 1)))
     random_ = random.Random()
     random_.seed(seed)
     state = [random_.choice((False, True)) for _ in range(bits)]
     if True not in state:
         state[random_.randint(0, len(state) - 1)] = True
     sequence = scipy.signal.max_len_seq(bits, state, length)[0]
     channels = sumpf_internal.allocate_array(shape=(1, len(sequence)), dtype=numpy.float64)
     channels[0, :] = sequence
     channels *= 2.0
     channels -= 1.0
     Signal.__init__(self, channels=channels, sampling_rate=sampling_rate, offset=0, labels=("MLS",))
     self.__bits = bits
Example #8
0
 def __algebra_function_other_has_one_channel(self, other, function, label):
     channels = sumpf_internal.allocate_array(shape=self.shape())
     function(self._channels, other.channels()[0], out=channels)
     return Signal(channels=channels,
                   sampling_rate=self.__sampling_rate,
                   offset=self.__offset,
                   labels=(label,) * len(self))
Example #9
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(),))
Example #10
0
    def decay_model(self, start, stop):
        """Computes an exponential decay curve, that is fitted to the segment of
        the energy decay curve, which is specified by the start and stop parameters.

        The :class:`~sumpf.Signal`, that is returned by this method, can be plotted
        alongside this energy decay curve in order to check, if the reverberation
        time, that is computed from the specified segment, is realistic.

        In order to produce a meaningful result, the start and stop parameters must
        frame the segment, in which the semi-logarithmic plot (dB plot) of the energy
        decay curve falls linearly over time.

        The given start and stop parameters are the indices of the respective samples
        of the signal. These indices can be given as (negative) integers, which
        work just like :class:`list` indices, or as floats between -1.0 and 1.0,
        which will be scaled to the length of the signal.

        :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 :class:`~sumpf.Signal`
        """
        channels = sumpf_internal.allocate_array(shape=self.shape())
        i = numpy.arange(self._length)
        for c, (m, n) in zip(channels, self.__solve(start, stop)):
            c[:] = numpy.power(10.0, (m / 10.0) * i + (n / 10.0))
        return Signal(channels=channels,
                      sampling_rate=self.sampling_rate(),
                      offset=self.offset(),
                      labels=self.labels())
Example #11
0
 def __init__(self,
              start_frequency=20.0,
              stop_frequency=20000.0,
              phase=0.0,
              interval=(0, 1.0),
              sampling_rate=48000.0,
              length=2**16):
     """
     :param start_frequency: the start frequency in Hz
     :param stop_frequency: the stop frequency in Hz
     :param phase: a phase offset in radians (e.g. pass pi/2 for a cosine sweep)
     :param interval: a tuple, list or array of two numbers, that specify the
                      indices of the samples, at which the start and the stop
                      frequencies shall be excited. The sweep will continue
                      outside this interval. This functionality is useful, if
                      the sweep shall be faded in or out and the range between
                      the start and the stop frequency shall not be affected
                      by the fading.
                      The indices can be specified as integer indices or as
                      floats between 0.0 and 1.0, which are mapped to 0 and
                      ``length``. Negative indices (also possible with floats)
                      are relative to the end of the signal.
     :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 sweep
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     # generate the sweep
     start, stop, t, T, sweep_offset, a, b, k = linear_sweep_parameters(
         start_frequency=start_frequency,
         stop_frequency=stop_frequency,
         interval=interval,
         sampling_rate=sampling_rate,
         length=length)
     t *= -1.0
     t += T
     s = 2.0 / (
         stop - start
     )  # a scaling factor, so that the convolution of the sweep and the inverse sweep results in a unit impulse
     if length <= 8192 or not numexpr:  # for short sweeps NumExpr is slower than NumPy
         array = t * t
         array *= b
         array += a * t
         array += phase + 0.0
         numpy.sin(array, out=channels[0, :])
         channels *= s
     else:
         numexpr.evaluate(ex="s * sin(phase + a * t + b * (t**2))",
                          out=channels[0, :],
                          optimization="moderate")
     # store the values and parameters
     Sweep.__init__(self,
                    channels=channels,
                    sampling_rate=sampling_rate,
                    offset=-stop - start,
                    function=lambda tau: stop_frequency - k *
                    (tau - sweep_offset))
Example #12
0
 def __algebra_function_other_has_one_channel(self, other, function, label):
     channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
     function(self._channels, other.channels()[0], out=channels)
     return Spectrogram(channels=channels,
                        resolution=self.__resolution,
                        sampling_rate=self.__sampling_rate,
                        offset=self.__offset,
                        labels=(label,) * len(self))
Example #13
0
    def conjugate(self):
        """Returns a spectrum with the complex conjugate of this spectrum's channels.

        :returns: a :class:`~sumpf.Spectrum`
        """
        channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
        numpy.conjugate(self._channels, out=channels)
        return Spectrum(channels=channels, resolution=self.__resolution, labels=self._labels)
Example #14
0
    def __abs__(self):
        """Operator overload for computing the magnitude of the spectrum with the
        built-in function :func:`abs`.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.absolute(self._channels, out=sumpf_internal.allocate_array(self.shape())),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #15
0
    def __neg__(self):
        """Operator for inverting the phase of the spectrum with ``-spectrum``.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.negative(self._channels, out=sumpf_internal.allocate_array(self.shape(),
                                                                                                  numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #16
0
 def __init__(self, seed, sampling_rate, length, label):
     """
     :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 noise signal
     """
     self.__seed = seed
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     channels[0, :] = self._function(length)
     Signal.__init__(self, channels=channels, sampling_rate=sampling_rate, offset=0, labels=(label,))
Example #17
0
    def __abs__(self):
        """Operator overload for computing the magnitude of the spectrum with the
        built-in function :func:`abs`.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.absolute(
            self._channels, out=sumpf_internal.allocate_array(self.shape())),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #18
0
    def __neg__(self):
        """Operator for inverting the phase of the spectrum with ``-spectrum``.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.negative(
            self._channels,
            out=sumpf_internal.allocate_array(self.shape(), numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #19
0
    def __neg__(self):
        """Operator overload for computing the sample-wise negative of the signal
        with ``-signal``.

        :returns: a :class:`~sumpf.Signal` instance
        """
        return Signal(channels=numpy.negative(self._channels, out=sumpf_internal.allocate_array(self.shape())),
                      sampling_rate=self.__sampling_rate,
                      offset=self.__offset,
                      labels=self._labels)
Example #20
0
    def __abs__(self):
        """Operator overload for computing the sample-wise absolute of the signal
        with the built-in function :func:`abs`.

        :returns: a :class:`~sumpf.Signal` instance
        """
        return Signal(channels=numpy.fabs(self._channels, out=sumpf_internal.allocate_array(self.shape())),
                      sampling_rate=self.__sampling_rate,
                      offset=self.__offset,
                      labels=self._labels)
Example #21
0
 def __init__(self, seed, sampling_rate, length, label):
     """
     :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 noise signal
     """
     self.__seed = seed
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     channels[0, :] = self._function(length)
     Signal.__init__(self, channels=channels, sampling_rate=sampling_rate, offset=0, labels=(label,))
Example #22
0
 def __algebra_function_different_type(self, other, function):
     channels = sumpf_internal.allocate_array(self.shape())
     try:
         function(self._channels, other, out=channels)
     except TypeError:
         return NotImplemented
     else:
         return Signal(channels=channels,
                       sampling_rate=self.__sampling_rate,
                       offset=self.__offset,
                       labels=self._labels)
Example #23
0
    def _algebra_function_right(self, other, function):
        """Protected helper function for overloading the right hand side operators.

        :param other: the object "on the left side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :returns: a :class:`~sumpf.Signal` instance
        """
        return Signal(channels=function(other, self._channels, out=sumpf_internal.allocate_array(self.shape())),
                      sampling_rate=self.__sampling_rate,
                      offset=self.__offset,
                      labels=self._labels)
Example #24
0
    def conjugate(self):
        """Returns a spectrum with the complex conjugate of this spectrum's channels.

        :returns: a :class:`~sumpf.Spectrum`
        """
        channels = sumpf_internal.allocate_array(shape=self.shape(),
                                                 dtype=numpy.complex128)
        numpy.conjugate(self._channels, out=channels)
        return Spectrum(channels=channels,
                        resolution=self.__resolution,
                        labels=self._labels)
Example #25
0
    def __invert__(self):
        """Rededicated operator overload for inverting this spectrum.
        The inverse of a spectrum is simply ``1 / spectrum``.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.divide(1.0,
                                              self._channels,
                                              out=sumpf_internal.allocate_array(self.shape(), numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #26
0
    def _algebra_function(self, other, function, other_pivot, label):
        """Protected helper function that implements the broadcasting of spectrums
        or arrays with different shapes when using the overloaded math operators.

        :param other: the object "on the other side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :param other_pivot: a default value, that is used as first operand for samples, where
                            only the other object has data (e.g. when the two spectrums don't
                            overlap due to different lengths). If ``other_pivot`` is ``None``,
                            the data from the other object is copied.
        :param label: the string label for the computed channels
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        if isinstance(other, Spectrum):
            if len(self) == 1:
                channels = sumpf_internal.allocate_array(shape=(len(other), self._length), dtype=numpy.complex128)
                function(self._channels[0], other.channels(), out=channels)
            elif len(other) == 1:
                channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
                function(self._channels, other.channels()[0], out=channels)
            elif len(self) < len(other):
                channels = sumpf_internal.allocate_array(shape=(len(other), self._length), dtype=numpy.complex128)
                function(self._channels, other.channels()[0:len(self)], out=channels[0:len(self)])
                if other_pivot is None:
                    channels[len(self):] = other.channels()[len(self):]
                else:
                    function(other_pivot, other.channels()[len(self):], out=channels[len(self):])
            else:
                channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
                function(self._channels[0:len(other)], other.channels(), out=channels[0:len(other)])
                channels[len(other):] = self._channels[len(other):]
            return Spectrum(channels=channels, resolution=self.__resolution, labels=(label,) * len(channels))
        else:   # other is an array or a number
            try:
                return Spectrum(channels=function(self._channels,
                                                  other,
                                                  out=sumpf_internal.allocate_array(self.shape(), numpy.complex128)),
                                resolution=self.__resolution,
                                labels=self._labels)
            except TypeError:
                return NotImplemented
Example #27
0
    def __invert__(self):
        """Rededicated operator overload for inverting this spectrum.
        The inverse of a spectrum is simply ``1 / spectrum``.

        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=numpy.divide(
            1.0,
            self._channels,
            out=sumpf_internal.allocate_array(self.shape(), numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #28
0
    def __neg__(self):
        """Operator for inverting the phase of the spectrogram with ``-spectrogram``.

        :returns: a :class:`sumpf.Spectrogram` instance
        """
        channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
        numpy.negative(self._channels, out=channels)
        return Spectrogram(channels=channels,
                           resolution=self.__resolution,
                           sampling_rate=self.__sampling_rate,
                           offset=self.__offset,
                           labels=self._labels)
Example #29
0
    def _algebra_function_right(self, other, function):
        """Protected helper function for overloading the right hand side operators.

        :param other: the object "on the left side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=function(other,
                                          self._channels,
                                          out=sumpf_internal.allocate_array(self.shape(), numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #30
0
    def conjugate(self):
        """Returns a spectrogram with the complex conjugate of this spectrogram's channels.

        :returns: a :class:`sumpf.Spectrogram`
        """
        channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
        numpy.conjugate(self._channels, out=channels)
        return Spectrogram(channels=channels,
                           resolution=self.__resolution,
                           sampling_rate=self.__sampling_rate,
                           offset=self.__offset,
                           labels=self._labels)
Example #31
0
 def __algebra_function_different_type(self, other, function):
     channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
     try:
         function(self._channels, other, out=channels)
     except TypeError:
         return NotImplemented
     else:
         return Spectrogram(channels=channels,
                            resolution=self.__resolution,
                            sampling_rate=self.__sampling_rate,
                            offset=self.__offset,
                            labels=self._labels)
Example #32
0
 def __init__(self,
              start_frequency=20.0,
              stop_frequency=20000.0,
              phase=0.0,
              interval=(0, 1.0),
              sampling_rate=48000.0,
              length=2**16):
     """
     :param start_frequency: the start frequency in Hz
     :param stop_frequency: the stop frequency in Hz
     :param phase: a phase offset in radians (e.g. pass pi/2 for a cosine sweep)
     :param interval: a tuple, list or array of two numbers, that specify the
                      indices of the samples, at which the start and the stop
                      frequencies shall be excited. The sweep will continue
                      outside this interval. This functionality is useful, if
                      the sweep shall be faded in or out and the range between
                      the start and the stop frequency shall not be affected
                      by the fading.
                      The indices can be specified as integer indices or as
                      floats between 0.0 and 1.0, which are mapped to 0 and
                      ``length``. Negative indices (also possible with floats)
                      are relative to the end of the signal.
     :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 sweep
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     # generate the sweep
     _, _, t, _, sweep_offset, l, a = exponential_sweep_parameters(
         start_frequency, stop_frequency, interval, sampling_rate, length)
     if length <= 8192 or not numexpr:  # for short sweeps NumExpr is slower than NumPy
         array = t
         array /= l
         numpy.expm1(array, out=array)
         array *= a
         array += phase
         numpy.sin(array, out=channels[0, :])
     else:
         numexpr.evaluate(ex="sin(a * expm1(t / l) + phase)",
                          out=channels[0, :],
                          optimization="moderate")
     # store the values and parameters
     BaseExponentialSweep.__init__(
         self,
         channels=channels,
         sampling_rate=sampling_rate,
         offset=0,
         function=lambda tau: start_frequency * math.exp(
             (tau - sweep_offset) / l),
         l=l)
Example #33
0
 def __init__(self, value=0.0, resolution=1.0, length=1):
     """
     :param value: the complex value of the samples
     :param resolution: the frequency resolution of the spectrum as a float
     :param length: the number of samples of the spectrum
     """
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.complex128)
     channels[:] = value
     Spectrum.__init__(self,
                       channels=channels,
                       resolution=resolution,
                       labels=("Constant", ))
Example #34
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)
Example #35
0
    def spectrum(self, resolution, length):
        """Samples the transfer functions with the given resolution and given number
        of samples and returns the result as a spectrum.

        :param resolution: the frequency resolution of the resulting spectrum
        :param length: the number of samples per channel of the resulting spectrum
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        frequencies = numpy.linspace(0.0, (length - 1) * resolution, length)
        s = S(frequencies)
        channels = sumpf_internal.allocate_array(shape=(len(self.__transfer_functions), length), dtype=numpy.complex128)
        for tf, c in zip(self.__transfer_functions, channels):
            tf(s, out=c)
        return sumpf.Spectrum(channels=channels, resolution=resolution, labels=self.__labels)
Example #36
0
 def __init__(self, impulse_response):
     """
     :param impulse_response: the impulse response :class:`~sumpf.Signal`, from
                              which this energy decay curve shall be computed.
     """
     channels = sumpf_internal.allocate_array(
         shape=impulse_response.shape())
     numpy.square(impulse_response.channels(), out=channels)
     numpy.cumsum(channels[:, ::-1], axis=1, out=channels[:, ::-1])
     Signal.__init__(self,
                     channels=channels,
                     sampling_rate=impulse_response.sampling_rate(),
                     offset=impulse_response.offset(),
                     labels=impulse_response.labels())
Example #37
0
 def __algebra_function_different_shapes(self, other, function, other_pivot, label):
     # allocate the channels array
     start = min(self.__offset, other.offset())
     stop = max(self.__offset + self._length, other.offset() + other.length())
     length = stop - start
     channelcount = max(len(self), len(other))
     shape = (channelcount, length)
     channels = sumpf_internal.allocate_array(shape)
     # copy the two signals
     channels[:] = 0.0
     channels[0:len(self), self.__offset - start:self.__offset + self._length - start] = self._channels
     if len(self) == 1:
         for c in range(1, len(channels)):
             channels[c, self.__offset - start:self.__offset + self._length - start] = self._channels[0]
     if other_pivot is None:
         channels[0:len(other), other.offset() - start:other.offset() + other.length() - start] = other.channels()
         if len(other) == 1:
             for c in range(1, len(channels)):
                 channels[c, other.offset() - start:other.offset() + other.length() - start] = other.channels()[0]
     else:
         channel_slice = slice(other.offset() - start, other.offset() + other.length() - start)
         function(other_pivot, other.channels(), out=channels[0:len(other), channel_slice])
         if len(other) == 1:
             for c in range(1, len(channels)):
                 channels[c, channel_slice] = channels[0, channel_slice]
     # apply the function to the overlapping parts of the two signals
     overlap_start = max(self.__offset, other.offset())
     overlap_stop = min(self.__offset + self._length, other.offset() + other.length())
     if overlap_start < overlap_stop:
         if len(self._channels) == 1:
             function(self._channels[0, overlap_start - self.__offset:overlap_stop - self.__offset],
                      other.channels()[:, overlap_start - other.offset():overlap_stop - other.offset()],
                      out=channels[:, overlap_start - start:overlap_stop - start])
         elif len(other.channels()) == 1:
             function(self._channels[:, overlap_start - self.__offset:overlap_stop - self.__offset],
                      other.channels()[0, overlap_start - other.offset():overlap_stop - other.offset()],
                      out=channels[:, overlap_start - start:overlap_stop - start])
         else:
             overlap_channelcount = min(len(self), len(other))
             function(self._channels[0:overlap_channelcount,
                                     overlap_start - self.__offset:overlap_stop - self.__offset],
                      other.channels()[0:overlap_channelcount,
                                       overlap_start - other.offset():overlap_stop - other.offset()],
                      out=channels[0:overlap_channelcount, overlap_start - start:overlap_stop - start])
     # assemble and return the result signal
     return Signal(channels=channels,
                   sampling_rate=self.__sampling_rate,
                   offset=start,
                   labels=(label,) * channelcount)
Example #38
0
    def __invert__(self):
        """Rededicated operator overload for inverting this signal.
        Convolving a signal with its inverse results in a unit impulse. The inverse
        is computing in the frequency domain: ``ifft(1 / fft(signal))``.

        :returns: a :class:`~sumpf.Signal` instance
        """
        channels = sumpf_internal.allocate_array(shape=self.shape())
        spectrum = numpy.fft.rfft(self._channels)
        inverse = numpy.divide(1.0, spectrum)
        channels[:] = numpy.fft.irfft(inverse)
        return Signal(channels=channels,
                      sampling_rate=self.__sampling_rate,
                      offset=-self.__offset,
                      labels=self._labels)
Example #39
0
 def __init__(self, value=0.0, sampling_rate=48000.0, length=2**16):
     """
     :param value: the float value of the samples
     :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 signal
     """
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     channels[:] = value
     Signal.__init__(self,
                     channels=channels,
                     sampling_rate=sampling_rate,
                     offset=0,
                     labels=("Constant", ))
Example #40
0
 def __init__(self, start_frequency=20.0, stop_frequency=20000.0, phase=0.0,
              interval=(0, 1.0), sampling_rate=48000.0, length=2 ** 16):
     """
     :param start_frequency: the start frequency in Hz
     :param stop_frequency: the stop frequency in Hz
     :param phase: a phase offset in radians (e.g. pass pi/2 for a cosine sweep)
     :param interval: a tuple, list or array of two numbers, that specify the
                      indices of the samples, at which the start and the stop
                      frequencies shall be excited. The sweep will continue
                      outside this interval. This functionality is useful, if
                      the sweep shall be faded in or out and the range between
                      the start and the stop frequency shall not be affected
                      by the fading.
                      The indices can be specified as integer indices or as
                      floats between 0.0 and 1.0, which are mapped to 0 and
                      ``length``. Negative indices (also possible with floats)
                      are relative to the end of the signal.
     :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 sweep
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     # generate the sweep
     start, stop, t, T, sweep_offset, a, b, k = linear_sweep_parameters(start_frequency=start_frequency,
                                                                        stop_frequency=stop_frequency,
                                                                        interval=interval,
                                                                        sampling_rate=sampling_rate,
                                                                        length=length)
     t *= -1.0
     t += T
     s = 2.0 / (stop - start)    # a scaling factor, so that the convolution of the sweep and the inverse sweep results in a unit impulse
     if length <= 8192 or not numexpr:   # for short sweeps NumExpr is slower than NumPy
         array = t * t
         array *= b
         array += a * t
         array += phase + 0.0
         numpy.sin(array, out=channels[0, :])
         channels *= s
     else:
         numexpr.evaluate(ex="s * sin(phase + a * t + b * (t**2))", out=channels[0, :], optimization="moderate")
     # store the values and parameters
     Sweep.__init__(self,
                    channels=channels,
                    sampling_rate=sampling_rate,
                    offset=-stop - start,
                    function=lambda tau: stop_frequency - k * (tau - sweep_offset))
Example #41
0
 def __init__(self, start_frequency=20.0, stop_frequency=20000.0, phase=0.0,
              interval=(0, 1.0), sampling_rate=48000.0, length=2 ** 16):
     """
     :param start_frequency: the start frequency in Hz
     :param stop_frequency: the stop frequency in Hz
     :param phase: a phase offset in radians (e.g. pass pi/2 for a cosine sweep)
     :param interval: a tuple, list or array of two numbers, that specify the
                      indices of the samples, at which the start and the stop
                      frequencies shall be excited. The sweep will continue
                      outside this interval. This functionality is useful, if
                      the sweep shall be faded in or out and the range between
                      the start and the stop frequency shall not be affected
                      by the fading.
                      The indices can be specified as integer indices or as
                      floats between 0.0 and 1.0, which are mapped to 0 and
                      ``length``. Negative indices (also possible with floats)
                      are relative to the end of the signal.
     :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 sweep
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     # generate the sweep
     _, _, t, _, sweep_offset, l, a = exponential_sweep_parameters(start_frequency,
                                                                   stop_frequency,
                                                                   interval,
                                                                   sampling_rate,
                                                                   length)
     if length <= 8192 or not numexpr:   # for short sweeps NumExpr is slower than NumPy
         array = t
         array /= l
         numpy.expm1(array, out=array)
         array *= a
         array += phase
         numpy.sin(array, out=channels[0, :])
     else:
         numexpr.evaluate(ex="sin(a * expm1(t / l) + phase)", out=channels[0, :], optimization="moderate")
     # store the values and parameters
     BaseExponentialSweep.__init__(self,
                                   channels=channels,
                                   sampling_rate=sampling_rate,
                                   offset=0,
                                   function=lambda tau: start_frequency * math.exp((tau - sweep_offset) / l),
                                   l=l)
Example #42
0
    def _algebra_function_right(self, other, function):
        """Protected helper function for overloading the right hand side operators.

        :param other: the object "on the left side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :returns: a :class:`~sumpf.Signal` instance
        """
        channels = sumpf_internal.allocate_array(shape=self.shape(), dtype=numpy.complex128)
        try:
            function(other, self._channels, out=channels)
        except TypeError:
            return NotImplemented
        else:
            return Spectrogram(channels=channels,
                               resolution=self.__resolution,
                               sampling_rate=self.__sampling_rate,
                               offset=self.__offset,
                               labels=self._labels)
Example #43
0
    def _algebra_function_right(self, other, function, other_pivot, label):
        """Protected helper function for overloading the right hand side operators.

        :param other: the object "on the left side of the operator"
        :param function: a function, that implements the computation for arrays (e.g. numpy.add)
        :param other_pivot: a default value, that is used as first operand for samples, where
                            only the other object has data (e.g. when the two spectrums don't
                            overlap due to different lengths). If ``other_pivot`` is ``None``,
                            the data from the other object is copied.
        :param label: the string label for the computed channels
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        return Spectrum(channels=function(other,
                                          self._channels,
                                          out=sumpf_internal.allocate_array(
                                              self.shape(), numpy.complex128)),
                        resolution=self.__resolution,
                        labels=self._labels)
Example #44
0
 def __init__(self, frequency=1000.0, phase=0.0, sampling_rate=48000.0, length=2 ** 16):
     """
     :param frequency: the sine wave's frequency in Hz
     :param phase: a phase offset in radians (e.g. pass pi/2 for a cosine signal)
     :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 sine wave
     """
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.float64)
     duration = (length - 1) / sampling_rate
     omega_t_plus_phase = numpy.linspace(phase, 2.0 * math.pi * frequency * duration + phase, length)
     numpy.sin(omega_t_plus_phase, out=channels[0])
     Wave.__init__(self,
                   frequency=frequency,
                   channels=channels,
                   sampling_rate=sampling_rate,
                   offset=0,
                   labels=("Sine",))
Example #45
0
    def spectrum(self, resolution=48000.0 / 4096, length=2049):
        """Samples the transfer functions with the given resolution and given number
        of samples and returns the result as a spectrum.

        :param resolution: the frequency resolution of the resulting spectrum
        :param length: the number of samples per channel of the resulting spectrum
        :returns: a :class:`~sumpf.Spectrum` instance
        """
        frequencies = numpy.linspace(0.0, (length - 1) * resolution, length)
        s = S(frequencies)
        channels = sumpf_internal.allocate_array(shape=(len(
            self.__transfer_functions), length),
                                                 dtype=numpy.complex128)
        for tf, c in zip(self.__transfer_functions, channels):
            tf(s, out=c)
        return sumpf.Spectrum(channels=channels,
                              resolution=resolution,
                              labels=self.__labels)
Example #46
0
    def inverse_fourier_transform(self):
        """Computes the channel-wise inverse Fourier transform of this spectrum.

        :returns: a :class:`~sumpf.Signal` instance
        """
        if self._length == 0:
            return sumpf.Signal(channels=numpy.empty(shape=(len(self), 0)),
                                sampling_rate=0.0,
                                offset=0,
                                labels=self._labels)
        length = max(1, (self._length - 1) * 2)
        sampling_rate = self.__resolution * length
        channels = sumpf_internal.allocate_array(shape=(len(self._channels), length))
        channels[:, :] = numpy.fft.irfft(self._channels, n=length)
        return sumpf.Signal(channels=channels,
                            sampling_rate=sampling_rate,
                            offset=0,
                            labels=self._labels)
Example #47
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(), ))
Example #48
0
    def inverse_fourier_transform(self):
        """Computes the channel-wise inverse Fourier transform of this spectrum.

        :returns: a :class:`~sumpf.Signal` instance
        """
        if self._length == 0:
            return sumpf.Signal(channels=numpy.empty(shape=(len(self), 0)),
                                sampling_rate=0.0,
                                offset=0,
                                labels=self._labels)
        length = max(1, (self._length - 1) * 2)
        sampling_rate = self.__resolution * length
        channels = sumpf_internal.allocate_array(shape=(len(self._channels),
                                                        length))
        channels[:, :] = numpy.fft.irfft(self._channels, n=length)
        return sumpf.Signal(channels=channels,
                            sampling_rate=sampling_rate,
                            offset=0,
                            labels=self._labels)
Example #49
0
    def pad(self, length, value=0.0):
        """Returns a spectrum with the given length, which is achieved by either
        cropping or appending the given value.

        :param length: the length of the resulting spectrum
        :param value: the value with which the spectrum shall be padded
        :returns: a padded or cropped :class:`~sumpf.Spectrum`
        """
        if length == self._length:
            return self
        else:
            channels = sumpf_internal.allocate_array(shape=(len(self), length),
                                                     dtype=numpy.complex128)
            if length < self._length:
                channels[:] = self._channels[:, 0:length]
            else:
                channels[:, 0:self._length] = self._channels
                channels[:, self._length:] = value
            return Spectrum(channels=channels,
                            resolution=self.__resolution,
                            labels=self._labels)
Example #50
0
 def __init__(self, start_frequency=0.0, stop_frequency=None, resolution=1.0, length=1):
     """
     :param start_frequency: the start frequency in Hz. All frequencies below
                             this will have zero magnitude.
     :param stop_frequency: the stop frequency in Hz or None to take the
                            spectrum's maximum frequency. All frequencies
                            above this will have zero magnitude.
     :param resolution: the resolution of the spectrum as a float
     :param length: the length of the spectrum
     """
     # interpret the start and stop frequency
     offset = min(int(round(start_frequency / resolution)), length)
     if stop_frequency is None:
         sequence_length = length - offset
     else:
         sequence_length = min(int(round(stop_frequency / resolution)), length) - offset
     # generate the channel
     channels = sumpf_internal.allocate_array(shape=(1, length), dtype=numpy.complex128)
     channel = channels[0]
     channel[0:offset] = 0.0
     if offset < length and sequence_length > 0:
         channel[offset] = 1.0
         if offset + 1 < length and sequence_length > 1:
             channel[offset + 1] = 1.0
             i = 2
             while i < sequence_length:
                 j = min(i // 2, sequence_length - i)
                 channel[offset + i:offset + i + j] = channel[offset:offset + j]
                 if i + i // 2 < sequence_length:
                     j = min(i, sequence_length - i)
                     channel[offset + i + i // 2:offset + i + j] = -channel[offset + i // 2:offset + j]
                 i *= 2
     channel[offset + sequence_length:] = 0.0
     # store the parameters
     Spectrum.__init__(self, channels=channels, resolution=resolution, labels=("Noise",))
     self.__sequence_length = sequence_length
Example #51
0
 def __init__(self,
              value=0.0,
              resolution=24000.0 / 2048,
              sampling_rate=48000.0 / 4096,
              number_of_frequencies=2049,
              length=32):
     """
     :param value: the complex value of the samples
     :param resolution: the frequency resolution of the spectrogram as a float
     :param sampling_rate: the sampling rate of the spectrogram in Hz as an integer or a float
     :param number_of_frequencies: the number of frequency bins of the spectrogram
     :param length: the number of time bins of the spectrogram
     """
     channels = sumpf_internal.allocate_array(shape=(1,
                                                     number_of_frequencies,
                                                     length),
                                              dtype=numpy.complex128)
     channels[:] = value
     Spectrogram.__init__(self,
                          channels=channels,
                          resolution=resolution,
                          sampling_rate=sampling_rate,
                          offset=0,
                          labels=("Constant", ))
Example #52
0
    def output(self):
        """Computes the merged data set and returns it.

        :returns: a data set, that contains all channels of the added data sets.
        """
        if not self.__data:
            raise RuntimeError("Nothing to merge")
        if len(self.__data) == 1:
            return next(iter(self.__data.values())
                        )  # simply return the first and only data set
        else:
            first = next(iter(self.__data.values()))
            number_of_channels = sum(len(s) for s in self.__data.values())
            labels = [""] * number_of_channels
            if isinstance(first, sumpf.Signal):
                merged_offset = min(s.offset() for s in self.__data.values())
                length = max(s.offset() + s.length()
                             for s in self.__data.values()) - merged_offset
                channels = sumpf_internal.allocate_array(
                    shape=(number_of_channels, length))
                for index, channel, offset, label in zip(
                        self.__indices(),
                    (c for d in self.__data.values() for c in d.channels()),
                    (d.offset() for d in self.__data.values()
                     for l in d.channels()),  # pylint: disable=line-too-long
                    (l for d in self.__data.values() for l in d.labels())):
                    start = offset - merged_offset
                    stop = start + len(channel)
                    channels[index, 0:start] = 0.0
                    channels[index, start:stop] = channel
                    channels[index, stop:] = 0.0
                    labels[index] = label
                return sumpf.Signal(channels=channels,
                                    sampling_rate=first.sampling_rate(),
                                    offset=merged_offset,
                                    labels=labels)
            elif isinstance(first, sumpf.Spectrum):
                length = max(s.length() for s in self.__data.values())
                channels = sumpf_internal.allocate_array(
                    shape=(number_of_channels, length), dtype=numpy.complex128)
                for index, channel, label in zip(
                        self.__indices(), (c for d in self.__data.values()
                                           for c in d.channels()),
                    (l for d in self.__data.values() for l in d.labels())):
                    channels[index, 0:len(channel)] = channel
                    channels[index, len(channel):] = 0.0
                    labels[index] = label
                return sumpf.Spectrum(channels=channels,
                                      resolution=first.resolution(),
                                      labels=labels)
            elif isinstance(first, sumpf.Spectrogram):
                merged_offset = min(s.offset() for s in self.__data.values())
                length = max(s.offset() + s.length()
                             for s in self.__data.values()) - merged_offset
                number_of_frequencies = max(s.number_of_frequencies()
                                            for s in self.__data.values())
                channels = sumpf_internal.allocate_array(
                    shape=(number_of_channels, number_of_frequencies, length),
                    dtype=numpy.complex128)
                channels[:] = 0.0
                for index, channel, offset, label in zip(
                        self.__indices(),
                    (c for d in self.__data.values() for c in d.channels()),
                    (d.offset() for d in self.__data.values()
                     for l in d.channels()),  # pylint: disable=line-too-long
                    (l for d in self.__data.values() for l in d.labels())):
                    channel_frequencies, channel_length = channel.shape
                    start = offset - merged_offset
                    stop = start + channel_length
                    channels[index, 0:channel_frequencies,
                             start:stop] = channel
                    labels[index] = label
                return sumpf.Spectrogram(channels=channels,
                                         resolution=first.resolution(),
                                         sampling_rate=first.sampling_rate(),
                                         offset=merged_offset,
                                         labels=labels)
            elif isinstance(first, sumpf.Filter):
                transfer_functions = [None] * number_of_channels
                for index, transfer_function, label in zip(
                        self.__indices(),
                    (tf for d in self.__data.values()
                     for tf in d.transfer_functions()),  # pylint: disable=line-too-long
                    (l for d in self.__data.values() for l in d.labels())):
                    transfer_functions[index] = transfer_function
                    labels[index] = label
                return sumpf.Filter(transfer_functions=transfer_functions,
                                    labels=labels)
            else:
                raise ValueError(
                    f"Cannot merge data sets of type {type(first)}")
Example #53
0
    def output(
        self
    ):  # noqa: C901; it's either a complex method or a lot of duplicated code
        # pylint: disable=too-many-branches,too-many-statements;
        """Computes the concatenated signal and returns it

        :returns: a Signal instance
        """
        if not self.__data:
            raise RuntimeError("Nothing to concatenate")
        if len(self.__data) == 1:
            return next(iter(self.__data.values())
                        )  # simply return the first and only data set
        else:
            first = next(iter(self.__data.values()))
            # find the start and stop samples of the data sets
            index = 0
            indices = []
            number_of_channels = 0
            for s in self.__data.values():
                start = index + s.offset()
                index = start + s.length()
                indices.append((start, index, s.channels()))
                number_of_channels = max(number_of_channels, len(s))
            indices.sort(key=lambda t: (t[0], t[1]))
            # allocate an array for the concatenated channels and copy the first data set
            offset = min(i[0] for i in indices)
            length = max(i[1] for i in indices) - offset
            start, stop, dataset_channels = indices[0]
            start -= offset
            stop -= offset
            if len(dataset_channels.shape) == 2:
                signal = True
                channels = sumpf_internal.allocate_array(
                    shape=(number_of_channels, length),
                    dtype=dataset_channels.dtype)
                channels[0:len(dataset_channels),
                         start:stop] = dataset_channels
                if len(dataset_channels) < number_of_channels:
                    channels[len(dataset_channels):, start:stop] = 0.0
            else:
                signal = False
                number_of_frequencies = max(i[2].shape[1] for i in indices)
                channels = sumpf_internal.allocate_array(
                    shape=(number_of_channels, number_of_frequencies, length),
                    dtype=dataset_channels.dtype)
                l, f = dataset_channels.shape[0:2]
                channels[0:l, 0:f, start:stop] = dataset_channels
                if f < number_of_frequencies:
                    channels[0:l, f:, start:stop] = 0.0
                if l < number_of_channels:
                    channels[l:, :, start:stop] = 0.0
            # copy the other data sets
            last_index = 0  # the maximum sample index, at which there is already data in the channels array
            for index, previous in zip(indices[1:], indices[0:-1]):
                start, stop, dataset_channels = index
                start -= offset
                stop -= offset
                l, f = dataset_channels.shape[0:2]
                last_index = max(last_index, previous[1] - offset)
                if start < last_index:  # the current data set overlaps with the previous one, add the samples in the overlapping region
                    if last_index >= stop:  # pylint: disable=no-else-continue
                        stop = start + dataset_channels.shape[-1]
                        if signal:
                            channels[0:l, start:stop] += dataset_channels
                        else:
                            channels[0:l, 0:f, start:stop] += dataset_channels
                        continue
                    else:
                        if signal:
                            channels[
                                0:l, start:
                                last_index] += dataset_channels[:,
                                                                0:last_index -
                                                                start]
                            dataset_channels = dataset_channels[:, last_index -
                                                                start:]
                        else:
                            channels[
                                0:l, 0:f, start:
                                last_index] += dataset_channels[:, :,
                                                                0:last_index -
                                                                start]
                            dataset_channels = dataset_channels[:, :,
                                                                last_index -
                                                                start:]
                        start = last_index
                elif start > last_index:  # a gap between the current data set and the previous one, fill it with zeros
                    if signal:
                        channels[:, last_index:start] = 0.0
                    else:
                        channels[:, :, last_index:start] = 0.0
                if signal:
                    channels[0:l, start:stop] = dataset_channels
                    if len(
                            dataset_channels
                    ) < number_of_channels:  # if the current signal has less channels than the other, fill the missing channels with zeros
                        channels[l:, start:stop] = 0.0
                else:
                    channels[0:l, 0:f, start:stop] = dataset_channels
                    if f < number_of_frequencies:
                        channels[0:l, f:, start:stop] = 0.0
                    if len(
                            dataset_channels
                    ) < number_of_channels:  # if the current signal has less channels than the other, fill the missing channels with zeros
                        channels[l:, :, start:stop] = 0.0
            # create and return the result
            if signal:
                return sumpf.Signal(
                    channels=channels,
                    sampling_rate=first.sampling_rate(),
                    offset=offset,
                    labels=tuple(f"Concatenation {i}"
                                 for i in range(1, number_of_channels + 1)))
            else:
                return sumpf.Spectrogram(
                    channels=channels,
                    resolution=first.resolution(),
                    sampling_rate=first.sampling_rate(),
                    offset=offset,
                    labels=tuple(f"Concatenation {i}"
                                 for i in range(1, number_of_channels + 1)))
Example #54
0
 def __init__(self,
              start_frequency=20.0,
              stop_frequency=20000.0,
              phase=0.0,
              interval=(0, 1.0),
              sampling_rate=48000.0,
              length=2**16):
     """
     :param start_frequency: the start frequency in Hz (this is usually the
                             lowest frequency, that is excited at the end of
                             an inverse sweep, but at the beginning of a regular
                             sweep)
     :param start_frequency: the stop frequency in Hz (this is usually the
                             highest frequency, that is excited at the beginning
                             of an inverse sweep, but at the end of a regular
                             sweep)
     :param phase: a phase offset in radians (e.g. pass pi/2 for a inverse
                   cosine sweep)
     :param interval: a tuple, list or array of two numbers, that specify the
                      indices of the samples, at which the start and the stop
                      frequencies shall be excited. The sweep will continue
                      outside this interval. This functionality is useful, if
                      the sweep shall be faded in or out and the range between
                      the start and the stop frequency shall not be affected
                      by the fading.
                      The indices can be specified as integer indices or as
                      floats between 0.0 and 1.0, which are mapped to 0 and
                      ``length``. Negative indices (also possible with floats)
                      are relative to the end of the signal.
     :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 inverse sweep
     """
     # allocate shared memory for the channels
     channels = sumpf_internal.allocate_array(shape=(1, length),
                                              dtype=numpy.float64)
     # generate the sweep
     start, stop, t, T, sweep_offset, l, a = exponential_sweep_parameters(
         start_frequency, stop_frequency, interval, sampling_rate, length)
     s = 2.0 * start_frequency / l / (
         stop_frequency - start_frequency
     ) / sampling_rate  # a scaling factor, so that the convolution of the sweep and the inverse sweep results in a unit impulse
     if length <= 8192 or not numexpr:  # for short sweeps NumExpr is slower than NumPy
         exponent = numpy.subtract(T, t, out=t)
         exponent /= l
         # compute the envelope
         envelope = numpy.exp(exponent)
         envelope *= s
         # compute the sweep
         sweep = numpy.expm1(exponent, out=exponent)
         sweep *= a
         sweep += phase
         numpy.sin(sweep, out=sweep)
         # combine the envelope and the sweep
         numpy.multiply(envelope, sweep, out=channels[0, :])
     else:
         envelope = "s * exp((T-t) / l)"
         sweep = "sin(a * expm1((T-t) / l) + phase)"
         numexpr.evaluate(ex=f"{envelope} * {sweep}",
                          out=channels[0, :],
                          optimization="moderate")
     # store the values and parameters
     BaseExponentialSweep.__init__(
         self,
         channels=channels,
         sampling_rate=sampling_rate,
         offset=-stop - start,
         function=lambda tau: stop_frequency * math.exp(
             (-tau + sweep_offset) / l),
         l=l)
Example #55
0
    def harmonic_impulse_response(self,
                                  impulse_response,
                                  harmonic,
                                  length=None):
        """Cuts out the impulse response of the given harmonic from an impulse
        response of a system, that has been measured with this sweep.

        :param impulse_response: the :class:`~sumpf.Signal` instance with the
                                 complete impulse response of the system. The
                                 signal's offset shall point to the beginning
                                 of the linear impulse response.
        :param harmonic: the integer order of the harmonic, that shall be cut out.
                         ``1`` is the linear part, while the harmonics with an order
                         of ``2`` and higher come from nonlinearities in the system
        :param length: the integer length, that the harmonic impulse response
                       shall have. This will be achieved by cropping or zero
                       padding. If this is None, the length will be determined
                       automatically and it will vary for the different orders
                       of harmonics.
        """
        offset = impulse_response.offset()
        sampling_rate = impulse_response.sampling_rate()
        # cut out the harmonic impulse response from the signal
        if harmonic == 1:
            channels = impulse_response.channels()[:, -offset:]
            remaining_delay = None
        else:
            delay = math.log(harmonic) * self.__l
            shift = math.ceil(delay * sampling_rate)
            start = -int(shift) - offset
            remaining_delay = shift / sampling_rate - delay
            if harmonic == 2:
                stop = -offset
            else:
                stop = -int(
                    math.floor(
                        math.log(harmonic - 1) * self.__l *
                        sampling_rate)) - offset
            if stop == 0 or (start < 0 and stop >= 0):  # pylint: disable=chained-comparison; can't see what's wrong with this
                stop = None
            channels = impulse_response.channels()[:, start:stop]
        # zero-pad or crop the impulse response, so it has the desired length
        if length is not None:
            channel_count, harmonic_length = channels.shape
            if harmonic_length < length:
                new_channels = sumpf_internal.allocate_array(
                    shape=(channel_count, length))
                new_channels[:, 0:harmonic_length] = channels
                new_channels[:, harmonic_length:] = 0.0
                if remaining_delay is not None:
                    apply_delay(channels=new_channels,
                                sampling_rate=sampling_rate,
                                delay=remaining_delay,
                                out=new_channels)
                channels = new_channels
            elif harmonic_length > channel_count:
                if remaining_delay is not None:
                    new_channels = sumpf_internal.allocate_array(
                        shape=(channel_count, length))
                    apply_delay(channels=channels,
                                sampling_rate=sampling_rate,
                                delay=remaining_delay,
                                out=new_channels)
                    channels = new_channels
                else:
                    channels = channels[:, 0:length]
        elif remaining_delay is not None:
            apply_delay(channels=channels,
                        sampling_rate=sampling_rate,
                        delay=remaining_delay,
                        out=channels)
        # return the impulse response of the harmonic
        h = sumpf_internal.counting_number(harmonic)
        return Signal(
            channels=channels,
            sampling_rate=sampling_rate,
            offset=0,
            labels=[f"{l} ({h} harmonic)" for l in impulse_response.labels()])
Example #56
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, ))