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,)
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]
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}")
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(),))
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()
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",))
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",))
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
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)
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)
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
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)
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
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
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())
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(), ))
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())
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
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
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
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", ))
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", ))
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)
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, ))