Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test Cases for MultiPitchKlapuri and MultiPitchMelodia #1141

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/algorithms/tonal/multipitchklapuri.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class MultiPitchKlapuri : public Algorithm {
declareParameter("magnitudeThreshold", "spectral peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter for the salience function (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered harmonics", "[1,inf)", 10);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch salience function peaks
declareParameter("minFrequency", "the minimum allowed frequency for salience function peaks (ignore peaks below) [Hz]", "[0,inf)", 80.0);
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms/tonal/multipitchmelodia.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class MultiPitchMelodia : public Algorithm {
declareParameter("magnitudeThreshold", "spectral peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter for the salience function (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered harmonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch contour tracking
declareParameter("peakFrameThreshold", "per-frame salience threshold factor (fraction of the highest peak salience in a frame)", "[0,1]", 0.9);
Expand Down Expand Up @@ -167,7 +167,7 @@ class MultiPitchMelodia : public AlgorithmComposite {
declareParameter("magnitudeThreshold", "peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered hamonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch salience function peaks
declareParameter("minFrequency", "the minimum allowed frequency for salience function peaks (ignore peaks below) [Hz]", "[0,inf)", 80.0);
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms/tonal/pitchmelodia.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class PitchMelodia : public Algorithm {
declareParameter("magnitudeThreshold", "spectral peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter for the salience function (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered harmonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch contour tracking
declareParameter("peakFrameThreshold", "per-frame salience threshold factor (fraction of the highest peak salience in a frame)", "[0,1]", 0.9);
Expand Down Expand Up @@ -170,7 +170,7 @@ class PitchMelodia : public AlgorithmComposite {
declareParameter("magnitudeThreshold", "peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered hamonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch salience function peaks
declareParameter("minFrequency", "the minimum allowed frequency for salience function peaks (ignore peaks below) [Hz]", "[0,inf)", 80.0);
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms/tonal/predominantpitchmelodia.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class PredominantPitchMelodia : public Algorithm {
declareParameter("magnitudeThreshold", "spectral peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter for the salience function (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered harmonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch contour tracking
declareParameter("peakFrameThreshold", "per-frame salience threshold factor (fraction of the highest peak salience in a frame)", "[0,1]", 0.9);
Expand Down Expand Up @@ -171,7 +171,7 @@ class PredominantPitchMelodia : public AlgorithmComposite {
declareParameter("magnitudeThreshold", "peak magnitude threshold (maximum allowed difference from the highest peak in dBs)", "[0,inf)", 40);
declareParameter("magnitudeCompression", "magnitude compression parameter (=0 for maximum compression, =1 for no compression)", "(0,1]", 1.0);
declareParameter("numberHarmonics", "number of considered hamonics", "[1,inf)", 20);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "(0,1)", 0.8);
declareParameter("harmonicWeight", "harmonic weighting parameter (weight decay ratio between two consequent harmonics, =1 for no decay)", "[0,1]", 0.8);

// pitch salience function peaks
declareParameter("minFrequency", "the minimum allowed frequency for salience function peaks (ignore peaks below) [Hz]", "[0,inf)", 80.0);
Expand Down
135 changes: 135 additions & 0 deletions test/src/unittests/tonal/test_multipitchklapuri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/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 numpy import *
from essentia_test import *


class TestMultiPitchKlapuri(TestCase):

def testZero(self):
signal = zeros(1024)
pitch = MultiPitchMelodia()(signal)
self.assertEqualVector(pitch, [0., 0., 0., 0., 0., 0., 0., 0., 0.])

def testInvalidParam(self):
self.assertConfigureFails(MultiPitchKlapuri(), {'binResolution': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'frameSize': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'harmonicWeight': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'hopSize': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'magnitudeCompression': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'magnitudeCompression': 2})
self.assertConfigureFails(MultiPitchKlapuri(), {'magnitudeThreshold': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'maxFrequency': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'minFrequency': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'numberHarmonics': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'referenceFrequency': -1})
self.assertConfigureFails(MultiPitchKlapuri(), {'sampleRate': -1})

def testOnes(self):
# FIXME. Need to derive a rational why this output occurs for a constant input
signal = ones(1024)
pitch = MultiPitchKlapuri()(signal)
expectedPitch= [[ 92.498886, 184.99854 ],
[108.110695, 151.1358 ],
[108.73698, 151.1358 ],
[108.73698, 151.1358 ],
[108.73698, 151.1358 ],
[108.110695, 151.1358 ],
[ 92.498886, 184.99854 ]]

self.assertEqual(len(pitch), 7)
index=0
while (index<len(expectedPitch)):
print(pitch[index])
self.assertAlmostEqualFixedPrecision(pitch[index], expectedPitch[index],0)
index+=1

def testEmpty(self):
pitch = MultiPitchKlapuri()([])
self.assertEqualVector(pitch, [])

def test110Hz(self):
# generate test signal: sine 110Hz @44100kHz
frameSize= 4096
signalSize = 10 * frameSize
signal = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 110 * 2*math.pi)
mpk = MultiPitchKlapuri()
pitch = mpk(signal)
index= int(len(pitch)/2) # Halfway point in pitch array
self.assertAlmostEqual(pitch[index], 110.0, 1)

def testMajorScale(self):
# generate test signal concatenating major scale notes.
frameSize= 2048
signalSize = 5 * frameSize

# Here are generate sine waves for each note of the scale, e.g. C3 is 130.81 Hz, etc
c3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 130.81 * 2*math.pi)
d3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 146.83 * 2*math.pi)
e3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 164.81 * 2*math.pi)
f3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 174.61 * 2*math.pi)
g3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 196.00 * 2*math.pi)
a3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 220.00 * 2*math.pi)
b3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 246.94 * 2*math.pi)
c4 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 261.63 * 2*math.pi)

# This signal is a "major scale ladder"
scale = concatenate([c3, d3, e3, f3, g3, a3, b3, c4])

mpk = MultiPitchKlapuri()
pitch = mpk(scale)

numPitchSamples = len(pitch)
numSinglePitchSamples = int(numPitchSamples/8)
midPointOffset = int(numSinglePitchSamples/2)

theLen = len(pitch)
index = 0
klapArray = []
while (index < theLen):
klapArray.append(pitch[index][0])
index+=1

# On each step of the "SCALE LADDER" we take the step mid point.
# We calculate array index mid point to allow checking the estimated pitch.

midpointC3 = midPointOffset
midpointD3 = int(1 * numSinglePitchSamples) + midPointOffset
midpointE3 = int(2 * numSinglePitchSamples) + midPointOffset
midpointF3 = int(3 * numSinglePitchSamples) + midPointOffset
midpointG3 = int(4 * numSinglePitchSamples) + midPointOffset
midpointA3 = int(5 * numSinglePitchSamples) + midPointOffset
midpointB3 = int(6 * numSinglePitchSamples) + midPointOffset
midpointC4 = int(7 * numSinglePitchSamples) + midPointOffset

self.assertAlmostEqualFixedPrecision(klapArray[midpointC3], 130.81, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointD3], 146.83, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointE3], 164.81, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointF3], 174.61, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointG3], 196.00, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointA3], 220.00, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointB3], 246.94, 1)
self.assertAlmostEqualFixedPrecision(klapArray[midpointC4], 261.63, 1)

suite = allTests(TestMultiPitchKlapuri)

if __name__ == '__main__':
TextTestRunner(verbosity=2).run(suite)
122 changes: 122 additions & 0 deletions test/src/unittests/tonal/test_multipitchmelodia.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/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 numpy import *
from essentia_test import *


class TestMultiPitchMelodia(TestCase):

def testInvalidParam(self):
self.assertConfigureFails(MultiPitchMelodia(), {'binResolution': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'filterIterations': 0})
self.assertConfigureFails(MultiPitchMelodia(), {'frameSize': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'harmonicWeight': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'hopSize': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'magnitudeCompression': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'magnitudeCompression': 2})
self.assertConfigureFails(MultiPitchMelodia(), {'magnitudeThreshold': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'maxFrequency': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'minDuration': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'minFrequency': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'numberHarmonics': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'peakDistributionThreshold': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'peakDistributionThreshold': 2.1})
self.assertConfigureFails(MultiPitchMelodia(), {'peakFrameThreshold': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'peakFrameThreshold': 2})
self.assertConfigureFails(MultiPitchMelodia(), {'pitchContinuity': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'referenceFrequency': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'sampleRate': -1})
self.assertConfigureFails(MultiPitchMelodia(), {'timeContinuity': -1})

def testEmpty(self):
pitch = MultiPitchMelodia()([])
self.assertEqualVector(pitch, [])

def testZero(self):
signal = zeros(1024)
pitch = MultiPitchMelodia()(signal)
self.assertEqualVector(pitch, [0., 0., 0., 0., 0., 0., 0., 0., 0.])

def testOnes(self):
signal = ones(1024)
pitch = MultiPitchMelodia()(signal)
expectedPitch=[[0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.], [0.]]
index=0
self.assertEqual(len(pitch), 9)
while (index<len(expectedPitch)):
self.assertEqualVector(pitch[index], expectedPitch[index])
index+=1

def testMajorScale(self):
# generate test signal concatenating major scale notes.
frameSize= 2048
signalSize = 5 * frameSize

# Here are generate sine waves for each note of the scale, e.g. C3 is 130.81 Hz, etc
c3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 130.81 * 2*math.pi)
d3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 146.83 * 2*math.pi)
e3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 164.81 * 2*math.pi)
f3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 174.61 * 2*math.pi)
g3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 196.00 * 2*math.pi)
a3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 220.00 * 2*math.pi)
b3 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 246.94 * 2*math.pi)
c4 = 0.5 * numpy.sin((array(range(signalSize))/44100.) * 261.63 * 2*math.pi)

# This signal is a "major scale ladder"
scale = concatenate([c3, d3, e3, f3, g3, a3, b3, c4])

mpm = MultiPitchMelodia()
pitch = mpm(scale)

numPitchSamples = len(pitch)
numSinglePitchSamples = int(numPitchSamples/8)
midPointOffset = int(numSinglePitchSamples/2)

theLen = len(pitch)
index = 0
multiArray = []
while (index < theLen):
multiArray.append(pitch[index][0])
index+=1

midpointC3 = midPointOffset
midpointD3 = int(1 * numSinglePitchSamples) + midPointOffset
midpointE3 = int(2 * numSinglePitchSamples) + midPointOffset
midpointF3 = int(3 * numSinglePitchSamples) + midPointOffset
midpointG3 = int(4 * numSinglePitchSamples) + midPointOffset
midpointA3 = int(5 * numSinglePitchSamples) + midPointOffset
midpointB3 = int(6 * numSinglePitchSamples) + midPointOffset
midpointC4 = int(7 * numSinglePitchSamples) + midPointOffset

self.assertAlmostEqualFixedPrecision(multiArray[midpointC3], 130.81, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointD3], 146.83, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointE3], 164.81, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointF3], 174.61, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointG3], 196.00, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointA3], 220.00, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointB3], 246.94, 1)
self.assertAlmostEqualFixedPrecision(multiArray[midpointC4], 261.63, 1)


suite = allTests(TestMultiPitchMelodia)

if __name__ == '__main__':
TextTestRunner(verbosity=2).run(suite)
Loading