Skip to content
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