From e5f720fc9b496e9995408dc8a9bea3a746e982e6 Mon Sep 17 00:00:00 2001 From: cvf-bcn-gituser Date: Sun, 25 Feb 2024 17:56:22 +0100 Subject: [PATCH 1/5] Adding Unit Test for Tonal Extractor --- .../extractor/test_tonalextractor.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 test/src/unittests/extractor/test_tonalextractor.py diff --git a/test/src/unittests/extractor/test_tonalextractor.py b/test/src/unittests/extractor/test_tonalextractor.py new file mode 100644 index 000000000..0bc59f6e3 --- /dev/null +++ b/test/src/unittests/extractor/test_tonalextractor.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python + +# Copyright (C) 2006-2021 Music Technology Group - Universitat Pompeu Fabra +# +# This file is part of Essentia +# +# Essentia is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation (FSF), either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the Affero GNU General Public License +# version 3 along with this program. If not, see http://www.gnu.org/licenses/ + +from essentia_test import * +import numpy as np + +class TestTonalExtractor(TestCase): + + def testEmpty(self): + # Test if the algorithm handles an empty input signal correctly + with self.assertRaises(RuntimeError): + chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength = TonalExtractor()(np.array([], dtype=np.float32)) + + def testSilence(self): + # In this test we jiuts check three of the output parameters of type real + silence_vec = np.zeros(44100, dtype=np.single) + chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength = TonalExtractor()(silence_vec) + self.assertEqual(chords_changes_rate, 0.0) + self.assertGreaterEqual(chords_number_rate, 0.0) + self.assertEqual(key_strength, 0.0) + + def testInvalidParameters(self): + # Test if the algorithm handles invalid parameters correctly + extractor = TonalExtractor() + + # Test case 1: Negative frameSize + with self.subTest(msg="Negative frameSize"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=-1, hopSize=2048, tuningFrequency=440.0) + + # Test case 2: Negative hopSize + with self.subTest(msg="Negative hopSize"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=4096, hopSize=-1, tuningFrequency=440.0) + + # Test case 3: Negative tuningFrequency + with self.subTest(msg="Negative tuningFrequency"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=4096, hopSize=2048, tuningFrequency=-440.0) + + # Test case 4: Zero frameSize and hopSize + with self.subTest(msg="Zero frameSize and hopSize"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=0, hopSize=0, tuningFrequency=440.0) + + # Test case 5: Zero frameSize + with self.subTest(msg="Zero frameSize"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=0, hopSize=2048, tuningFrequency=440.0) + + # Test case 6: Zero hopSize + with self.subTest(msg="Zero hopSize"): + with self.assertRaises(RuntimeError): + extractor.configure(frameSize=4096, hopSize=0, tuningFrequency=440.0) + + # Test case 7: Non-negative parameters + with self.subTest(msg="Valid parameters"): + # This should not raise an exception + extractor.configure(frameSize=4096, hopSize=2048, tuningFrequency=440.0) + + def testRandomInput(self): + n = 10 + for _ in range(n): + rand_input = np.random.random(88200).astype(np.single) * 2 - 1 + result = TonalExtractor()(rand_input) + expected_result = np.sum(rand_input * rand_input) ** 0.67 + self.assertAlmostEqual(result[0], expected_result, 9.999e+02) + + def testRegression(self): + frameSizes = [128, 256, 512, 1024, 2048, 4096] + hopSizes = [256, 512, 1024, 2048, 4096, 8192] + + input_filename = join(testdata.audio_dir, "recorded", "dubstep.wav") # Replace 'testdata' with actual path + real_audio_input = MonoLoader(filename=input_filename)() + + + # Iterate through pairs of frameSize and corresponding hopSize + for fs, hs in zip(frameSizes, hopSizes): + with self.subTest(frameSize=fs, hopSize=hs): + # Process the algorithm on real audio with the current frameSize and hopSize + te = TonalExtractor() + te.configure(frameSize=fs, hopSize=hs) + chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength= te(real_audio_input) + + # Perform assertions on one or more outputs + # Example: Assert that chords_changes_rate is a non-negative scalar + self.assertIsInstance(chords_changes_rate, (int, float)) + self.assertGreaterEqual(chords_changes_rate, 0) + self.assertIsInstance(chords_number_rate, (int, float)) + self.assertGreaterEqual(chords_number_rate, 0) + self.assertIsInstance(key_strength, (int, float)) + self.assertGreaterEqual(key_strength, 0) + # You can add more assertions on other outputs as needed + + +suite = allTests(TestTonalExtractor) + +if __name__ == '__main__': + unittest.main() From 2d56ff239696c0cc1e8669535f24ca03240740ea Mon Sep 17 00:00:00 2001 From: cvf-bcn-gituser Date: Tue, 27 Feb 2024 22:22:51 +0100 Subject: [PATCH 2/5] QA Unit tests: Add test on real Audio for tonal extractor algorithm --- .../extractor/test_tonalextractor.py | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/test/src/unittests/extractor/test_tonalextractor.py b/test/src/unittests/extractor/test_tonalextractor.py index 0bc59f6e3..e31c274eb 100644 --- a/test/src/unittests/extractor/test_tonalextractor.py +++ b/test/src/unittests/extractor/test_tonalextractor.py @@ -16,10 +16,12 @@ # # You should have received a copy of the Affero GNU General Public License # version 3 along with this program. If not, see http://www.gnu.org/licenses/ +import os.path from essentia_test import * import numpy as np + class TestTonalExtractor(TestCase): def testEmpty(self): @@ -83,20 +85,20 @@ def testRandomInput(self): self.assertAlmostEqual(result[0], expected_result, 9.999e+02) def testRegression(self): - frameSizes = [128, 256, 512, 1024, 2048, 4096] - hopSizes = [256, 512, 1024, 2048, 4096, 8192] + frameSizes = [256, 512, 1024, 2048, 4096, 8192] + hopSizes = [128, 256, 512, 1024, 2048, 4096] input_filename = join(testdata.audio_dir, "recorded", "dubstep.wav") # Replace 'testdata' with actual path - real_audio_input = MonoLoader(filename=input_filename)() - + realAudio = MonoLoader(filename=input_filename)() # Iterate through pairs of frameSize and corresponding hopSize + # TODO: Extend loop to try different tuningFrequency values for fs, hs in zip(frameSizes, hopSizes): with self.subTest(frameSize=fs, hopSize=hs): # Process the algorithm on real audio with the current frameSize and hopSize te = TonalExtractor() te.configure(frameSize=fs, hopSize=hs) - chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength= te(real_audio_input) + chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength= te(realAudio) # Perform assertions on one or more outputs # Example: Assert that chords_changes_rate is a non-negative scalar @@ -108,6 +110,43 @@ def testRegression(self): self.assertGreaterEqual(key_strength, 0) # You can add more assertions on other outputs as needed + def testRealAudio(self): + + # These reference values could also be compared with th results of tonal extractors of alternative a + # audio libraries (e.g. MadMom, libs fromn Alexander Lerch etc.) + # ccr = chord changes rate ; cnr = chord number rate; ks = key strength + mozart_ccr = 0.03400309011340141 + mozart_cnr = 0.010819165036082268 + mozart_ks = 0.8412253260612488 + + vivaldi_ccr = 0.052405908703804016 + vivaldi_cnr = 0.004764173645526171 + vivaldi_ks = 0.7122617959976196 + + thresh = 0.5 + + def test_on_real_audio(path, ccr, cnr, ks): + realAudio = MonoLoader(filename=path)() + + # Use default configuration of algorothm + # This function could be extended to test for more outputs + # TODO: Extend to test non-scalar and string outputs: + # i.e. chords_histogram, chords_progression, chords_scale, chords_strength + # hpcp, hpcp_highres, key_key and key_scale + te = TonalExtractor() + chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength= te(realAudio) + self.assertIsInstance(chords_changes_rate, (int, float)) + self.assertGreaterEqual(chords_changes_rate, 0) + self.assertAlmostEqual(chords_changes_rate, ccr, thresh) + self.assertIsInstance(chords_number_rate, (int, float)) + self.assertGreaterEqual(chords_number_rate, 0) + self.assertAlmostEqual(chords_number_rate, cnr, thresh) + self.assertIsInstance(key_strength, (int, float)) + self.assertGreaterEqual(key_strength, 0) + self.assertAlmostEqual(key_strength, ks, thresh) + + test_on_real_audio(join(testdata.audio_dir, "recorded", "mozart_c_major_30sec.wav"), mozart_ccr, mozart_cnr, mozart_ks) + test_on_real_audio(join(testdata.audio_dir, "recorded", "Vivaldi_Sonata_5_II_Allegro.wav"), vivaldi_ccr, vivaldi_cnr, vivaldi_ks) suite = allTests(TestTonalExtractor) From aabc2f8d697ed9be340ad1de8f3bf6bbc676dc18 Mon Sep 17 00:00:00 2001 From: cvf-bcn-gituser Date: Tue, 27 Feb 2024 22:27:06 +0100 Subject: [PATCH 3/5] QA Unit tests: spelling errors in comments --- test/src/unittests/extractor/test_tonalextractor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/src/unittests/extractor/test_tonalextractor.py b/test/src/unittests/extractor/test_tonalextractor.py index e31c274eb..1d8272c93 100644 --- a/test/src/unittests/extractor/test_tonalextractor.py +++ b/test/src/unittests/extractor/test_tonalextractor.py @@ -30,7 +30,7 @@ def testEmpty(self): chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength = TonalExtractor()(np.array([], dtype=np.float32)) def testSilence(self): - # In this test we jiuts check three of the output parameters of type real + # In this test we check three of the output parameters of type real silence_vec = np.zeros(44100, dtype=np.single) chords_changes_rate, _, _, chords_number_rate, _, _, _, _, _, _, _, key_strength = TonalExtractor()(silence_vec) self.assertEqual(chords_changes_rate, 0.0) @@ -112,8 +112,8 @@ def testRegression(self): def testRealAudio(self): - # These reference values could also be compared with th results of tonal extractors of alternative a - # audio libraries (e.g. MadMom, libs fromn Alexander Lerch etc.) + # These reference values could also be compared with the results of tonal extractors of alternative + # audio libraries (e.g. MadMom, libs from Alexander Lerch etc.) # ccr = chord changes rate ; cnr = chord number rate; ks = key strength mozart_ccr = 0.03400309011340141 mozart_cnr = 0.010819165036082268 From e2bbecaf38037bb11cdd45d725e34ab0ddeb94f1 Mon Sep 17 00:00:00 2001 From: cvf-bcn-gituser Date: Wed, 31 Dec 2025 11:50:02 +0100 Subject: [PATCH 4/5] remove warning in replaygain destructor, added unit tests --- src/algorithms/standard/replaygain.cpp | 2 +- .../src/unittests/standard/test_replaygain.py | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/algorithms/standard/replaygain.cpp b/src/algorithms/standard/replaygain.cpp index 6ee0ac551..9b1cc8c98 100644 --- a/src/algorithms/standard/replaygain.cpp +++ b/src/algorithms/standard/replaygain.cpp @@ -127,7 +127,7 @@ ReplayGain::ReplayGain() : _applyEqloud(false) { ReplayGain::~ReplayGain() { - if (!_network) { + if (_network) { _network->deleteAlgorithms(); delete _network; } diff --git a/test/src/unittests/standard/test_replaygain.py b/test/src/unittests/standard/test_replaygain.py index 6bf244ef6..e616cb8c7 100644 --- a/test/src/unittests/standard/test_replaygain.py +++ b/test/src/unittests/standard/test_replaygain.py @@ -20,11 +20,63 @@ from essentia_test import * +import gc +import weakref #testdir = join(filedir(), 'replaygain') class TestReplayGain(TestCase): + def testInitialization(self): + rg = ReplayGain() + self.assertIsInstance(rg, ReplayGain) + + def testDefaultSampleRate(self): + rg = ReplayGain() + print (rg.parameter("sampleRate")) + self.assertEqual(rg.parameter("sampleRate"), 44100) + + def testConfigureViaCompute(self): + sampleRate = 48000 + input = [0.0] * int(sampleRate * 0.1) + + rg = ReplayGain(sampleRate=sampleRate) + gain = rg(input) + + self.assertIsInstance(gain, float) + + def testReset(self): + sampleRate = 44100 + input = [0.0] * int(sampleRate * 0.1) + + rg = ReplayGain(sampleRate=sampleRate) + first = rg(input) + + rg.reset() + second = rg(input) + + self.assertAlmostEqual(first, second) + + def testMultipleComputesProduceDifferentResults(self): + sampleRate = 44100 + rg = ReplayGain(sampleRate=sampleRate) + + input1 = [0.0] * int(sampleRate * 0.1) + input2 = [0.1] * int(sampleRate * 0.1) + + g1 = rg(input1) + g2 = rg(input2) + + self.assertNotEqual(g1, g2) + + def testDestructor(self): + rg = ReplayGain(sampleRate=44100) + ref = weakref.ref(rg) + + del rg + gc.collect() + self.assertIsNone(ref()) + def testZero(self): sampleRate = 44100 input = [0.0] * int(sampleRate * 1) From d1b220321e21b6cd511c1df1d65ae33d6c9cb885 Mon Sep 17 00:00:00 2001 From: cvf-bcn-gituser Date: Wed, 31 Dec 2025 14:14:46 +0100 Subject: [PATCH 5/5] update python --- src/algorithms/rhythm/harmonicbpm.cpp | 2 +- src/algorithms/standard/vectorrealtotensor.cpp | 2 +- test/src/unittests/standard/test_replaygain.py | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/algorithms/rhythm/harmonicbpm.cpp b/src/algorithms/rhythm/harmonicbpm.cpp index 74f941efb..46cacd454 100644 --- a/src/algorithms/rhythm/harmonicbpm.cpp +++ b/src/algorithms/rhythm/harmonicbpm.cpp @@ -75,7 +75,7 @@ vector HarmonicBpm::findHarmonicBpms(const vector& bpms) { while (i::max(); - Real bestBpm; + Real bestBpm = -1; // Select the best candidate among aproximately the same (within tolerance) BPMs. while (i < (int)harmonicBpms.size() && diff --git a/src/algorithms/standard/vectorrealtotensor.cpp b/src/algorithms/standard/vectorrealtotensor.cpp index 44b1918af..75c238fb7 100644 --- a/src/algorithms/standard/vectorrealtotensor.cpp +++ b/src/algorithms/standard/vectorrealtotensor.cpp @@ -218,7 +218,7 @@ AlgorithmStatus VectorRealToTensor::process() { // or if we have reached the end of the stream with lastBatchModel = "push" // and there are not enough patches to fill a regular batch. - } else if (shouldStop() and _acc.size() < _shape[0]) { + } else if (shouldStop() and _acc.size() < static_cast(_shape[0])) { reshapeBatch = true; } diff --git a/test/src/unittests/standard/test_replaygain.py b/test/src/unittests/standard/test_replaygain.py index e616cb8c7..3c9de3798 100644 --- a/test/src/unittests/standard/test_replaygain.py +++ b/test/src/unittests/standard/test_replaygain.py @@ -31,11 +31,6 @@ def testInitialization(self): rg = ReplayGain() self.assertIsInstance(rg, ReplayGain) - def testDefaultSampleRate(self): - rg = ReplayGain() - print (rg.parameter("sampleRate")) - self.assertEqual(rg.parameter("sampleRate"), 44100) - def testConfigureViaCompute(self): sampleRate = 48000 input = [0.0] * int(sampleRate * 0.1)