def test_interval(): """Tests if the interval functionality of the ExponentialSweep class works""" sweep1 = sumpf.ExponentialSweep(interval=(200, 800), length=2**10) sweep2 = sumpf.ExponentialSweep(length=600) assert sweep1[:, 200:800].channels() == pytest.approx(sweep2.channels()) sweep3 = sumpf.ExponentialSweep(interval=(0.1, -0.1), length=2**10) sweep4 = sumpf.ExponentialSweep( length=int(round(2**10 * 0.9) - round(2**10 * 0.1))) assert sweep3[:, 0.1:-0.1].channels() == pytest.approx(sweep4.channels())
def test_ports_and_connections_when_deactivated(): """tests the creation of connections and ports while the client is deactivated.""" with _create_client() as client: client.inports.register("input") client.outports.register("output") # test creating and connecting ports, when the client is deactivated signal1 = sumpf.MergeSignals([ sumpf.SineWave(length=2**12), sumpf.HannWindow(length=2**12), sumpf.ExponentialSweep(length=2**12) ]).output() xruns = _XRunHandler() sumpf_jack = sumpf.Jack(name="port_creation", input_signal=signal1) sumpf_jack.xruns.connect(xruns.xrun) sumpf_jack.add_input_port("index1") sumpf_jack.add_input_port("shortname") sumpf_jack.add_input_port("name") sumpf_jack.add_input_port("index2") sumpf_jack.connect(0, 0) sumpf_jack.connect("Hann window", "shortname") sumpf_jack.connect("port_creation:Sweep", "port_creation:name") sumpf_jack.connect(0, "Testclient:input") sumpf_jack.connect("Testclient:output", 3) sumpf_jack.start() reference = sumpf.MergeSignals([ signal1, signal1[0, 0:-client.blocksize].shift(client.blocksize) ]).output() assert sumpf_jack.output().channels() == pytest.approx( reference.channels()) # test that the connections remain established, when the output ports change signal2 = sumpf.MergeSignals([ sumpf.BartlettWindow(length=2**12), sumpf.ExponentialSweep(length=2**12), sumpf.SineWave(length=2**12) ]).output() sumpf_jack.input(signal2) sumpf_jack.start() reference = sumpf.MergeSignals([ signal2, signal2[0, 0:-client.blocksize].shift(client.blocksize) ]).output() assert sumpf_jack.output().channels() == pytest.approx( reference.channels()) # test that the connections are broken, when the input ports are changed sumpf_jack.remove_input_port("index1") sumpf_jack.remove_input_port("shortname") sumpf_jack.remove_input_port("name") sumpf_jack.remove_input_port("index2") sumpf_jack.add_input_port("a") sumpf_jack.add_input_port("b") sumpf_jack.add_input_port("c") sumpf_jack.add_input_port("d") sumpf_jack.start() assert (sumpf_jack.output().channels() == numpy.zeros( shape=sumpf_jack.output().shape())).all()
def test_output_ports(): """Tests if the output ports of the :class:`~sumpf.Jack` instances are created from the labels of their input signal. """ with _create_client() as client: xruns = _XRunHandler() jack = sumpf.Jack("CUT") # Client Under Test jack.xruns.connect(xruns.xrun) # check the output port for the empty default input signal assert ["CUT:output_1"] == [ p.name for p in client.get_ports(is_output=True) if p.name.startswith("CUT:") ] # check adding and renaming output ports jack.input( sumpf.MergeSignals([sumpf.BetaNoise(), sumpf.SquareWave()]).output()) assert ["CUT:Beta noise", "CUT:Square wave"] == [p.name for p in client.get_ports(is_output=True) if p.name.startswith("CUT:")] # pylint: disable=line-too-long # check removing and renaming output ports jack.input(sumpf.ExponentialSweep()) assert ["CUT:Sweep"] == [ p.name for p in client.get_ports(is_output=True) if p.name.startswith("CUT:") ] # check generating port names from a signal with crooked labels jack.input( sumpf.Signal(channels=numpy.eye(3), labels=(None, "output_1")) ) # one label None, one label already exists as port name and one label is missing assert ["CUT:output_1", "CUT:output_2", "CUT:output_3"] == [p.name for p in client.get_ports(is_output=True) if p.name.startswith("CUT:")] # pylint: disable=line-too-long assert xruns.xruns == []
def test_autodetect_format_on_reading(): """Tests if auto-detecting the file format, when reading a file works.""" signal = sumpf.ExponentialSweep() * 0.9 with tempfile.TemporaryDirectory() as d: for file_format in sumpf.Signal.file_formats: if file_format != sumpf.Signal.file_formats.AUTO: path = os.path.join(d, "test_file") assert not os.path.exists(path) try: signal.save(path, file_format) except ValueError: # no writer for the given file found pass else: loaded = sumpf.Signal.load(path) assert loaded.shape() == signal.shape() os.remove(path)
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_instantaneous_frequency(start_frequency, stop_frequency, sampling_rate, length, interval_start, interval_stop): """Tests the instantaneous_frequency method of the ExponentialSweep class""" sweep = sumpf.ExponentialSweep(start_frequency=start_frequency, stop_frequency=stop_frequency, interval=(interval_start, interval_stop), sampling_rate=sampling_rate, length=length) assert sweep.instantaneous_frequency(0.0) == sweep.minimum_frequency() assert sweep.instantaneous_frequency(round(interval_start * length) / sampling_rate) == pytest.approx(start_frequency) # pylint: disable=line-too-long assert sweep.instantaneous_frequency(round(interval_stop * length) / sampling_rate) == pytest.approx(stop_frequency) # pylint: disable=line-too-long assert sweep.instantaneous_frequency( sweep.duration()) == sweep.maximum_frequency() diff = numpy.diff(sweep.instantaneous_frequency(sweep.time_samples())) assert (diff > 0).all() # the frequency should increase monotonically
def test_compare_with_other_formula(start_frequency, stop_frequency, sine, sampling_rate, length): """Compares the current implementation with a sweep, that is created by a different but equivalent formula.""" if sine: phase = 0.0 else: phase = math.pi / 2.0 sweep1 = sumpf.ExponentialSweep(start_frequency=start_frequency, stop_frequency=stop_frequency, phase=phase, sampling_rate=sampling_rate, length=length) sweep2 = other_sweep_formula(start_frequency=start_frequency, stop_frequency=stop_frequency, sampling_rate=sampling_rate, length=length, sine=sine) assert sweep1.channels() == pytest.approx(sweep2.channels(), abs=1e-7)
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.ExponentialSweep(start_frequency=start_frequency, stop_frequency=stop_frequency, interval=(start, stop), sampling_rate=sampling_rate, length=length) isweep = sumpf.InverseExponentialSweep(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 test_convolution_with_sweep(): """Tests if the convolution with the respective sweep results in a unit impulse""" for kwargs in ({ "start_frequency": 300.0, "length": 1024 }, { "stop_frequency": 12747.0, "length": 1024 }, { "phase": 2.9, "length": 1024 }, { "interval": (0.15, 0.9), "length": 2048 }, { "stop_frequency": 5000.0, "sampling_rate": 18312.7, "length": 1024 }): a, b = kwargs.get("interval", (0, 1.0)) sweep = sumpf.ExponentialSweep(**kwargs) isweep = sumpf.InverseExponentialSweep(**kwargs) impulse = sweep[:, a:b].convolve(isweep[:, a:b]) peak = max(impulse.channels()[0]) peak_index = impulse.channels()[0].argmax() other = max( max(abs(impulse.channels()[0, 0:-impulse.offset() - 1]) ), # the maximum of the absolute values of the impulse except max(abs(impulse.channels()[ 0, -impulse.offset() + 2:]))) # for the sample at t=0 and the two neighboring samples assert abs(peak - 1.0 ) < 0.005 # the highest peak should be one (unit impulse) assert peak_index == -impulse.offset( ) # the highest peak should be at time value 0 assert peak > 5 * abs( other ) # other peaks and notches should be much smaller than the impulse
def test_autodetect_format_on_saving(): """Tests if auto-detecting the file format from the file extension, when writing a file works.""" try: import soundfile # noqa; pylint: disable=unused-import,import-outside-toplevel; this shall raise an ImportError, if the soundfile library cannot be imported except ImportError: file_formats = [(sumpf.Signal.file_formats.TEXT_CSV, ".csv", signal_readers.CsvReader), (sumpf.Signal.file_formats.TEXT_JSON, ".json", signal_readers.JsonReader), (sumpf.Signal.file_formats.TEXT_JSON, ".js", signal_readers.JsonReader), (sumpf.Signal.file_formats.NUMPY_NPZ, ".npz", signal_readers.NumpyReader), (sumpf.Signal.file_formats.NUMPY_NPY, ".npy", signal_readers.NumpyReader), (sumpf.Signal.file_formats.PYTHON_PICKLE, ".pickle", signal_readers.PickleReader), (sumpf.Signal.file_formats.WAV_INT32, ".wav", signal_readers.WaveReader), (sumpf.Signal.file_formats.AIFF_INT32, ".aiff", signal_readers.AifcReader), (sumpf.Signal.file_formats.AIFF_INT32, ".aifc", signal_readers.AifcReader), (sumpf.Signal.file_formats.AIFF_INT32, ".aif", signal_readers.AifcReader)] else: file_formats = [(sumpf.Signal.file_formats.TEXT_CSV, ".csv", signal_readers.CsvReader), (sumpf.Signal.file_formats.TEXT_JSON, ".json", signal_readers.JsonReader), (sumpf.Signal.file_formats.TEXT_JSON, ".js", signal_readers.JsonReader), (sumpf.Signal.file_formats.NUMPY_NPZ, ".npz", signal_readers.NumpyReader), (sumpf.Signal.file_formats.NUMPY_NPY, ".npy", signal_readers.NumpyReader), (sumpf.Signal.file_formats.PYTHON_PICKLE, ".pickle", signal_readers.PickleReader), (sumpf.Signal.file_formats.WAV_FLOAT32, ".wav", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.AIFF_FLOAT32, ".aiff", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.AIFF_FLOAT32, ".aifc", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.AIFF_FLOAT32, ".aif", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.FLAC_INT24, ".flac", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.OGG_VORBIS, ".ogg", signal_readers.SoundfileReader), (sumpf.Signal.file_formats.OGG_VORBIS, ".oga", signal_readers.SoundfileReader)] signal = sumpf.ExponentialSweep() * 0.9 with tempfile.TemporaryDirectory() as d: for file_format, ending, Reader in file_formats: reader = Reader() auto_path = os.path.join(d, "test_file" + ending) reference_path = os.path.join(d, "test_file") assert not os.path.exists(auto_path) assert not os.path.exists(reference_path) signal.save(auto_path) signal.save(reference_path, file_format) auto_loaded = sumpf.Signal.load(auto_path) reader_loaded = reader(auto_path) reference = reader(reference_path) assert auto_loaded.shape() == signal.shape() assert auto_loaded == reader_loaded assert reader_loaded.sampling_rate() == reference.sampling_rate() assert reader_loaded.offset() == reference.offset() assert (reader_loaded.channels() == reference.channels()).all() os.remove(auto_path) os.remove(reference_path)
def test_harmonic_impulse_response(): """Does some trivial tests with the harmonic_impulse_response method""" # create a sweep, an inverse sweep and a distorted version of the sweep sweep = sumpf.ExponentialSweep(start_frequency=20.0, stop_frequency=7800.0, sampling_rate=48000, length=2**14) inverse = sumpf.InverseExponentialSweep(start_frequency=20.0, stop_frequency=7800.0, sampling_rate=48000, length=2**14) # pylint: disable=line-too-long distorted = 0.5 * sweep**3 - 0.6 * sweep**2 + 0.1 * sweep + 0.02 lowpass = sumpf.ButterworthFilter(cutoff_frequency=1000.0, order=16, highpass=False) highpass = sumpf.ButterworthFilter(cutoff_frequency=1000.0, order=16, highpass=True) response = distorted * highpass * lowpass # test with non-circular deconvolution impulse_response = response.convolve( inverse, mode=sumpf.Signal.convolution_modes.SPECTRUM_PADDED) h1 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=1) h2 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=2, length=h1.length()) h3 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=3) assert h1.length() == h2.length( ) # check if the length parameter has worked assert h3.length() < h2.length( ) # the impulse responses of the higher order harmonics are shorter harmonics = sumpf.Merge([h1, h2, h3]).output() spectrum = harmonics.fourier_transform() magnitude = spectrum.magnitude() max_indices = magnitude.argmax(axis=1) assert max(max_indices) - min( max_indices ) <= 1 # the maximums of all harmonics' transfer functions should be roughly the same assert max_indices.mean() * spectrum.resolution() == pytest.approx( 1000.0, rel=1e-3) # the maximums should be around 1000Hz # test with circular deconvolution impulse_response = response.convolve( inverse, mode=sumpf.Signal.convolution_modes.SPECTRUM).shift(None) h1 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=1, length=2048) h2 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=2, length=2048) h3 = sweep.harmonic_impulse_response(impulse_response=impulse_response, harmonic=3, length=2048) assert h1.length() == 2048 assert h2.length() == 2048 assert h3.length() == 2048 harmonics = sumpf.Merge([h1, h2, h3]).output() spectrum = harmonics.fourier_transform() magnitude = spectrum.magnitude() max_indices = magnitude.argmax(axis=1) assert max(max_indices) - min( max_indices ) <= 5 # the maximums of all harmonics' transfer functions should be roughly the same assert max_indices.mean() * spectrum.resolution() == pytest.approx( 1000.0, rel=8e-3) # the maximums should be around 1000Hz