-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unit test update: convert to unit tests, add negative examples.
- Loading branch information
Showing
11 changed files
with
203 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import unittest | ||
|
||
|
||
if __name__ == '__main__': | ||
root_dir = './' | ||
loader = unittest.TestLoader() | ||
testSuite = loader.discover(start_dir='tests', | ||
pattern="test_*.py") | ||
|
||
runner = unittest.TextTestRunner(verbosity=2) | ||
runner.run(testSuite) |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,58 @@ | ||
import numpy as np | ||
import soundfile as sf | ||
from unittest import TestCase | ||
|
||
|
||
from libsoni.core import f0 | ||
|
||
Fs = 22050 | ||
C_MAJOR_SCALE = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 0.0] | ||
DURATIONS = [None, 3.0, 5.0] | ||
PARTIALS = [np.array([1]), np.array([1, 2, 3])] | ||
PARTIALS_AMPLITUDES = [np.array([1]), np.array([1, 0.5, 0.25])] | ||
|
||
|
||
def test_f0(): | ||
time_positions = np.arange(0.2, len(C_MAJOR_SCALE) * 0.5, 0.5) | ||
time_f0 = np.column_stack((time_positions, C_MAJOR_SCALE)) | ||
|
||
for duration in DURATIONS: | ||
for par_idx, partials in enumerate(PARTIALS): | ||
if duration is None: | ||
duration_in_samples = None | ||
else: | ||
duration_in_samples = int(duration * Fs) | ||
|
||
y = f0.sonify_f0(time_f0=time_f0, | ||
partials=partials, | ||
partials_amplitudes=PARTIALS_AMPLITUDES[par_idx], | ||
sonification_duration=duration_in_samples, | ||
fs=Fs) | ||
|
||
ref, _ = sf.read(f'tests/data/f0_{duration_in_samples}_{par_idx}.wav') | ||
assert len(y) == len(ref), 'Length of the generated sonification does not match with the reference!' | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) | ||
|
||
class TestF0(TestCase): | ||
def setUp(self) -> None: | ||
c_major_scale = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 0.0] | ||
time_positions = np.arange(0.2, len(c_major_scale) * 0.5, 0.5) | ||
self.fs = 22050 | ||
self.durations = [int(3.0*self.fs), int(5.0*self.fs)] | ||
self.partials = [np.array([1]), np.array([1, 2, 3])] | ||
self.partials_amplitudes = [np.array([1]), np.array([1, 0.5, 0.25])] | ||
self.time_f0 = np.column_stack((time_positions, c_major_scale)) | ||
|
||
def test_input_types(self) -> None: | ||
[self.assertIsInstance(duration, int) for duration in self.durations] | ||
[self.assertIsInstance(partials, np.ndarray) for partials in self.partials] | ||
[self.assertIsInstance(partials_amplitude, np.ndarray) for partials_amplitude in self.partials_amplitudes] | ||
self.assertIsInstance(self.fs, int) | ||
self.assertIsInstance(self.time_f0, np.ndarray) | ||
|
||
def test_input_shape(self) -> None: | ||
with self.assertRaises(IndexError) as context: | ||
_ = f0.sonify_f0(time_f0=np.zeros(1)) | ||
self.assertEqual(str(context.exception), 'time_f0 must be a numpy array of size [N, 2]') | ||
|
||
with self.assertRaises(IndexError) as context: | ||
_ = f0.sonify_f0(time_f0=np.zeros((3, 3))) | ||
self.assertEqual(str(context.exception), 'time_f0 must be a numpy array of size [N, 2]') | ||
|
||
def test_invalid_partial_sizes(self): | ||
with self.assertRaises(ValueError) as context: | ||
_ = f0.sonify_f0(time_f0=self.time_f0, | ||
partials=self.partials[0], | ||
partials_amplitudes=self.partials_amplitudes[1], | ||
sonification_duration=self.durations[0], | ||
fs=self.fs) | ||
|
||
self.assertEqual(str(context.exception), 'Partials, Partials_amplitudes and Partials_phase_offsets must be ' | ||
'of equal length.') | ||
|
||
def test_sonification(self) -> None: | ||
for duration in self.durations: | ||
for par_idx, partials in enumerate(self.partials): | ||
y = f0.sonify_f0(time_f0=self.time_f0, | ||
partials=self.partials[par_idx], | ||
partials_amplitudes=self.partials_amplitudes[par_idx], | ||
sonification_duration=duration, | ||
fs=self.fs) | ||
|
||
ref, _ = sf.read(f'tests/data/f0_{duration}_{par_idx}.wav') | ||
self.assertEqual(len(y), len(ref), msg='Length of the generated sonification ' | ||
'does not match with the reference!') | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,63 @@ | ||
import numpy as np | ||
import soundfile as sf | ||
from unittest import TestCase | ||
|
||
from libsoni.core.methods import generate_click, generate_shepard_tone, generate_sinusoid | ||
from libsoni.core.methods import generate_click, generate_shepard_tone, generate_sinusoid,\ | ||
generate_tone_instantaneous_phase | ||
from libsoni.util.utils import pitch_to_frequency | ||
|
||
DURATIONS = [0.2, 0.5, 1.0] | ||
FADE = [0.05, 0.1] | ||
PITCHES = [60, 69] | ||
Fs = 22050 | ||
|
||
|
||
def test_click(): | ||
for duration in DURATIONS: | ||
for pitch in PITCHES: | ||
y = generate_click(pitch=pitch, | ||
click_fading_duration=duration) | ||
|
||
ref, _ = sf.read(f'tests/data/click_{pitch}_{(int(Fs * duration))}.wav') | ||
|
||
assert len(y) == len(ref), 'Length of the generated sonification does not match with the reference!' | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) | ||
|
||
|
||
def test_sinusoid(): | ||
for duration in DURATIONS: | ||
for pitch in PITCHES: | ||
for fading_duration in FADE: | ||
freq = pitch_to_frequency(pitch=pitch) | ||
y = generate_sinusoid(frequency=freq, | ||
duration=duration, | ||
fading_duration=fading_duration) | ||
dur_samples = int(Fs * duration) | ||
fade_samples = int(Fs * fading_duration) | ||
ref, _ = sf.read(f'tests/data/sin_{pitch}_{dur_samples}_{fade_samples}.wav') | ||
assert len(y) == len(ref), 'Length of the generated sonification does not match with the reference!' | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) | ||
|
||
|
||
def test_shepard_tone(): | ||
for duration in DURATIONS: | ||
for pitch in PITCHES: | ||
for fading_duration in FADE: | ||
pitch_class = pitch % 12 | ||
y = generate_shepard_tone(pitch_class=pitch_class, | ||
duration=duration, | ||
fading_duration=fading_duration) | ||
dur_samples = int(Fs * duration) | ||
fade_samples = int(Fs * fading_duration) | ||
ref, _ = sf.read(f'tests/data/shepard_{pitch_class}_{dur_samples}_{fade_samples}.wav') | ||
assert len(y) == len(ref), 'Length of the generated sonification does not match with the reference!' | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) | ||
|
||
class TestMethods(TestCase): | ||
def setUp(self) -> None: | ||
self.frequency_vector = np.array([261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 0.0]) | ||
self.pitches = [60, 69] | ||
self.fade_vals = [0.05, 0.1] | ||
self.fs = 22050 | ||
self.durations = [int(0.2*self.fs), int(0.5*self.fs), int(1.0*self.fs)] | ||
self.partials = [np.array([1]), np.array([1, 2, 3])] | ||
self.partials_amplitudes = [np.array([1]), np.array([1, 0.5, 0.25])] | ||
|
||
def test_input_types(self) -> None: | ||
[self.assertIsInstance(duration, int) for duration in self.durations] | ||
[self.assertIsInstance(freq, float) for freq in self.frequency_vector] | ||
[self.assertIsInstance(partials, np.ndarray) for partials in self.partials] | ||
[self.assertIsInstance(partials_amplitude, np.ndarray) for partials_amplitude in self.partials_amplitudes] | ||
self.assertIsInstance(self.fs, int) | ||
|
||
def test_invalid_partial_sizes(self) -> None: | ||
with self.assertRaises(ValueError) as context: | ||
_ = generate_tone_instantaneous_phase(self.frequency_vector, | ||
partials=self.partials[0], | ||
partials_amplitudes=self.partials_amplitudes[1], | ||
fs=self.fs) | ||
|
||
self.assertEqual(str(context.exception), 'Partials, Partials_amplitudes and Partials_phase_offsets must be ' | ||
'of equal length.') | ||
|
||
def test_click(self) -> None: | ||
for duration in self.durations: | ||
for pitch in self.pitches: | ||
for fade_val in self.fade_vals: | ||
freq = pitch_to_frequency(pitch=pitch) | ||
y = generate_sinusoid(frequency=freq, | ||
duration=duration/self.fs, | ||
fading_duration=fade_val) | ||
fade_samples = int(self.fs * fade_val) | ||
ref, _ = sf.read(f'tests/data/sin_{pitch}_{duration}_{fade_samples}.wav') | ||
self.assertEqual(len(y), len(ref), msg='Length of the generated sonification ' | ||
'does not match with the reference!') | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) | ||
|
||
def test_shepard_tone(self) -> None: | ||
for duration in self.durations: | ||
for pitch in self.pitches: | ||
for fade_val in self.fade_vals: | ||
pitch_class = pitch % 12 | ||
y = generate_shepard_tone(pitch_class=pitch_class, | ||
duration=duration/self.fs, | ||
fading_duration=fade_val) | ||
fade_samples = int(self.fs * fade_val) | ||
ref, _ = sf.read(f'tests/data/shepard_{pitch_class}_{duration}_{fade_samples}.wav') | ||
self.assertEqual(len(y), len(ref), msg='Length of the generated sonification ' | ||
'does not match with the reference!') | ||
assert np.allclose(y, ref, atol=1e-4, rtol=1e-5) |
Oops, something went wrong.