From e7544ae359583d71f61bbf397d288d17993d6820 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 22 May 2024 14:18:46 +0200 Subject: [PATCH 01/23] Add hz2midi, midi2hz, hz2cents, midi2note and db2velocity in essentiamath.h --- src/essentia/essentiamath.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 2cb47cf03..f8c4e1665 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -742,6 +742,43 @@ inline Real hz2cents(Real hz) { return 12 * std::log(hz/440)/std::log(2.) + 69; } +inline Real hz2cents(Real frequencyA, Real frequencyB) { + return 1200 * log2(frequencyA / frequencyB); +} + +inline int hz2midi(Real hz, Real tuningFrequency) { + return 69 + (int) round(log2(hz / tuningFrequency) * 12); +} + +// TODO: check MIDI standard conventions + +inline std::string midi2note(int midiNoteNumber, std::vector allNotes) { + int noteIdx = midiNoteNumber - 69; + int idx = abs(noteIdx) % 12; + int octave = 4 + floor((noteIdx + 9) / 12.0); + if (noteIdx < 0) { + idx = abs(idx - 12) % 12; + } + std::string closest_note = allNotes[idx] + std::to_string(octave); + return closest_note; +} + +inline Real midi2hz(int midiNoteNumber) { + return 440.0 * powf(2, (midiNoteNumber - 69) / 12.0); +} + +inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { + return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); +} + +inline int db2velocity (Real decibels, Real hearingThreshold) { + int velocity = 0; + if (decibels > hearingThreshold) { + velocity = (int)((hearingThreshold - decibels) * 127 / hearingThreshold); // decibels should be negative + } + return velocity; +} + inline int argmin(const std::vector& input) { if (input.empty()) throw EssentiaException("trying to get argmin of empty array"); From 08133037fc2fab816fe6bb3cbbb37adf217545bf Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 22 May 2024 16:41:16 +0200 Subject: [PATCH 02/23] Add python binding and unittest for hz2midi() --- src/python/essentia/utils.py | 5 +++ src/python/globalfuncs.cpp | 14 ++++++++ test/src/unittests/base/test_utils.py | 52 +++++++++++++++------------ 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index ff8678186..fa3347c40 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -63,6 +63,10 @@ def mel2hz(arg): def hz2mel(arg): return _essentia.hz2mel( _c.convertData(arg, _c.Edt.REAL) ) +def hz2midi(arg1, arg2): + return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL), + _c.convertData(arg2, _c.Edt.REAL) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -86,6 +90,7 @@ def derivative(array): 'amp2db', 'db2amp', 'bark2hz', 'hz2bark', 'mel2hz', 'hz2mel', + 'hz2midi', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index edfa0e9ae..8d6bdadc8 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -389,6 +389,19 @@ hzToMel(PyObject* notUsed, PyObject* arg) { return PyFloat_FromDouble( double(mel) ); } +static PyObject* +hzToMidi(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (Real hertz, Real tuningFrequency)"); + return NULL; + } + + int midi = hz2midi( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + return PyLong_FromLong( int(midi) ); +} static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { @@ -1001,6 +1014,7 @@ static PyMethodDef Essentia__Methods[] = { { "hz2bark", hzToBark, METH_O, "Converts a frequency in Hz to a bark band" }, { "mel2hz", melToHz, METH_O, "Converts a mel band to frequency in Hz" }, { "hz2mel", hzToMel, METH_O, "Converts a frequency in Hz to a mel band" }, + { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 875133007..0b733098c 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -18,48 +18,49 @@ # version 3 along with this program. If not, see http://www.gnu.org/licenses/ - from essentia_test import * import math -import cmath # for asinh +import cmath # for asinh import sys + class TestUtils(TestCase): def testIsSilent(self): - self.assertEqual(True, isSilent([0]*100)) + self.assertEqual(True, isSilent([0] * 100)) def testInstantPower(self): sample = list(range(1, 11)) p = 0 for s in sample: p += s**2 - p /= float( len(sample) ) + p /= float(len(sample)) self.assertAlmostEqual(p, instantPower(sample)) def testIsPowerOfTwo(self): self.assertTrue(isPowerTwo(0)) - top =131072 - k=1 + top = 131072 + k = 1 while k < top: self.assertTrue(isPowerTwo(k)) - k*=2 + k *= 2 while k < top: - k=2*k+1 + k = 2 * k + 1 self.assertTrue(not isPowerTwo(k)) def testNextPowerOfTwo(self): self.assertEqual(nextPowerTwo(0), 0) self.assertEqual(nextPowerTwo(1), 1) - top =131072 - k=2 + top = 131072 + k = 2 lastPowerTwo = 2 while k < top: - if not isPowerTwo(k): self.assertEqual(nextPowerTwo(k), 2*lastPowerTwo) + if not isPowerTwo(k): + self.assertEqual(nextPowerTwo(k), 2 * lastPowerTwo) else: self.assertEqual(nextPowerTwo(k), k) - lastPowerTwo=k - k +=1 + lastPowerTwo = k + k += 1 def testLinToDb(self): lin = 12.34 @@ -68,8 +69,8 @@ def testLinToDb(self): def testDbToLin(self): db = -45.5 - expected_lin = 10**(db/10.) - self.assertAlmostEqual(expected_lin, db2lin(db) , 5e-7) + expected_lin = 10 ** (db / 10.0) + self.assertAlmostEqual(expected_lin, db2lin(db), 5e-7) def testPowToDb(self): pow = 12.34 @@ -78,8 +79,8 @@ def testPowToDb(self): def testDbToPow(self): db = -45.5 - expected_pow = 10**(db/10.) - self.assertAlmostEqual(expected_pow, db2pow(db) , 5e-7) + expected_pow = 10 ** (db / 10.0) + self.assertAlmostEqual(expected_pow, db2pow(db), 5e-7) def testAmpToDb(self): amp = 12.34 @@ -88,8 +89,8 @@ def testAmpToDb(self): def testDbToAmp(self): db = -45.5 - expected_amp = 10**(0.5*db/10.) - self.assertAlmostEqual(expected_amp, db2amp(db) , 5e-7) + expected_amp = 10 ** (0.5 * db / 10.0) + self.assertAlmostEqual(expected_amp, db2amp(db), 5e-7) def testBarkToHz(self): bark = 5 @@ -98,21 +99,26 @@ def testBarkToHz(self): def testHzToBark(self): hz = 440 - expected_bark = 26.81*hz / (1960 + hz) - 0.53 + expected_bark = 26.81 * hz / (1960 + hz) - 0.53 self.assertAlmostEqual(expected_bark, hz2bark(hz)) def testMelToHz(self): mel = 5 - expected_hz = 700.0*(math.exp(mel/1127.01048)-1.0) + expected_hz = 700.0 * (math.exp(mel / 1127.01048) - 1.0) self.assertAlmostEqual(expected_hz, mel2hz(mel)) def testHzToMel(self): hz = 440 - expected_mel = 1127.01048*math.log(hz/700.0+1.0) + expected_mel = 1127.01048 * math.log(hz / 700.0 + 1.0) self.assertAlmostEqual(expected_mel, hz2mel(hz)) + def testHzToMidi(self): + hz = 440 + expected_midi = 69 + self.assertAlmostEqual(expected_midi, hz2midi(hz, hz)) + suite = allTests(TestUtils) -if __name__ == '__main__': +if __name__ == "__main__": TextTestRunner(verbosity=2).run(suite) From 9bbc41da503b54966138ced0efe1e2c84f3adb28 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 22 May 2024 16:52:16 +0200 Subject: [PATCH 03/23] Add unittest for midi2hz() --- src/python/essentia/utils.py | 6 +++++- src/python/globalfuncs.cpp | 14 ++++++++++++++ test/src/unittests/base/test_utils.py | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index fa3347c40..dea788a33 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -63,6 +63,10 @@ def mel2hz(arg): def hz2mel(arg): return _essentia.hz2mel( _c.convertData(arg, _c.Edt.REAL) ) +def midi2hz(arg1, arg2): + return _essentia.midi2hz( _c.convertData(arg1, _c.Edt.INTEGER), + _c.convertData(arg2, _c.Edt.REAL) ) + def hz2midi(arg1, arg2): return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) @@ -90,7 +94,7 @@ def derivative(array): 'amp2db', 'db2amp', 'bark2hz', 'hz2bark', 'mel2hz', 'hz2mel', - 'hz2midi', + 'midi2hz', 'hz2midi', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index 8d6bdadc8..dd4d0a521 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -389,6 +389,19 @@ hzToMel(PyObject* notUsed, PyObject* arg) { return PyFloat_FromDouble( double(mel) ); } +static PyObject* +midiToHz(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyLong_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (int midiNoteNumber, Real tuningFrequency)"); + return NULL; + } + Real hz = midi2hz( long( PyLong_AsLong(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + return PyFloat_FromDouble( double(hz) ); +} + static PyObject* hzToMidi(PyObject* notUsed, PyObject* args) { // parse args to get Source alg, name and source alg and source name @@ -1014,6 +1027,7 @@ static PyMethodDef Essentia__Methods[] = { { "hz2bark", hzToBark, METH_O, "Converts a frequency in Hz to a bark band" }, { "mel2hz", melToHz, METH_O, "Converts a mel band to frequency in Hz" }, { "hz2mel", hzToMel, METH_O, "Converts a frequency in Hz to a mel band" }, + { "midi2hz", midiToHz, METH_VARARGS, "Converts a midi note number to frequency in Hz" }, { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 0b733098c..696876dc1 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -117,6 +117,11 @@ def testHzToMidi(self): expected_midi = 69 self.assertAlmostEqual(expected_midi, hz2midi(hz, hz)) + def testMidiToHz(self): + expected_hz = tuning_frequency = 440 + midi = 69 + self.assertAlmostEqual(expected_hz, midi2hz(midi, tuning_frequency)) + suite = allTests(TestUtils) From 905fd16b8c3368eccbea06dc2369a226700d8536 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 22 May 2024 16:57:48 +0200 Subject: [PATCH 04/23] Replace old hz2cents() by hz2midi() --- src/algorithms/tonal/pitchyinprobabilities.cpp | 2 +- src/essentia/essentiamath.h | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/algorithms/tonal/pitchyinprobabilities.cpp b/src/algorithms/tonal/pitchyinprobabilities.cpp index 479af15ce..7518ff6d4 100644 --- a/src/algorithms/tonal/pitchyinprobabilities.cpp +++ b/src/algorithms/tonal/pitchyinprobabilities.cpp @@ -321,7 +321,7 @@ void PitchYinProbabilities::compute() { bool isLowAmplitude = (RMS < _lowAmp); for (size_t iCandidate = 0; iCandidate < _freq.size(); ++iCandidate) { - Real pitchCents = hz2cents(_freq[iCandidate]); + Real pitchCents = hz2midi(_freq[iCandidate]); _freq[iCandidate] = pitchCents; if (isLowAmplitude) { // lower the probabilities of the frequencies by calculating the weighted sum diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index f8c4e1665..be10f52c6 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -738,10 +738,6 @@ inline Real hz2hz(Real hz){ return hz; } -inline Real hz2cents(Real hz) { - return 12 * std::log(hz/440)/std::log(2.) + 69; -} - inline Real hz2cents(Real frequencyA, Real frequencyB) { return 1200 * log2(frequencyA / frequencyB); } @@ -763,10 +759,6 @@ inline std::string midi2note(int midiNoteNumber, std::vector allNot return closest_note; } -inline Real midi2hz(int midiNoteNumber) { - return 440.0 * powf(2, (midiNoteNumber - 69) / 12.0); -} - inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); } From 4b061bffa888c5ae5cdeef93478fa6ad54c35792 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Thu, 23 May 2024 13:24:53 +0200 Subject: [PATCH 05/23] Add hz2cents unittest --- src/python/essentia/utils.py | 5 +++++ src/python/globalfuncs.cpp | 15 +++++++++++++++ test/src/unittests/base/test_utils.py | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index dea788a33..f942f511e 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -71,6 +71,10 @@ def hz2midi(arg1, arg2): return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) +def hz2cents(arg1, arg2): + return _essentia.hz2cents(_c.convertData(arg1, _c.Edt.REAL), + _c.convertData(arg2, _c.Edt.REAL) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -95,6 +99,7 @@ def derivative(array): 'bark2hz', 'hz2bark', 'mel2hz', 'hz2mel', 'midi2hz', 'hz2midi', + 'hz2cents', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index dd4d0a521..7144fbfed 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -416,6 +416,20 @@ hzToMidi(PyObject* notUsed, PyObject* args) { return PyLong_FromLong( int(midi) ); } +static PyObject* +hzToCents(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + + if (argsV.size() != 2 || !PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1])) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real frequencyA, Real frequencyB)"); + return NULL; + } + + int cents = hz2cents( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); + return PyFloat_FromDouble( int(cents) ); +} + static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { if (!PyString_Check(arg)) { @@ -1029,6 +1043,7 @@ static PyMethodDef Essentia__Methods[] = { { "hz2mel", hzToMel, METH_O, "Converts a frequency in Hz to a mel band" }, { "midi2hz", midiToHz, METH_VARARGS, "Converts a midi note number to frequency in Hz" }, { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, + { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 696876dc1..ce6c76bb5 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -122,6 +122,12 @@ def testMidiToHz(self): midi = 69 self.assertAlmostEqual(expected_hz, midi2hz(midi, tuning_frequency)) + def testHzToCents(self): + tuning = 440 + midi = 70 + expected_cents = 100 + self.assertAlmostEqual(expected_cents, hz2cents(midi2hz(midi, tuning), tuning)) + suite = allTests(TestUtils) From b4267542358a2290727a83c37e3ad5a8dc26a7cf Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Fri, 24 May 2024 16:28:01 +0200 Subject: [PATCH 06/23] Harcode all_notes vector in midi2note() --- src/essentia/essentiamath.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index be10f52c6..80d029e5d 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -746,16 +746,15 @@ inline int hz2midi(Real hz, Real tuningFrequency) { return 69 + (int) round(log2(hz / tuningFrequency) * 12); } -// TODO: check MIDI standard conventions - -inline std::string midi2note(int midiNoteNumber, std::vector allNotes) { +inline std::string midi2note(int midiNoteNumber) { + const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; int noteIdx = midiNoteNumber - 69; int idx = abs(noteIdx) % 12; int octave = 4 + floor((noteIdx + 9) / 12.0); if (noteIdx < 0) { idx = abs(idx - 12) % 12; } - std::string closest_note = allNotes[idx] + std::to_string(octave); + std::string closest_note = ALL_NOTES[idx] + std::to_string(octave); return closest_note; } From 2191b6d05a23da93fe983a72bae3255608ace412 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Fri, 24 May 2024 16:32:59 +0200 Subject: [PATCH 07/23] Add cents2hz --- src/essentia/essentiamath.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 80d029e5d..20ce687e7 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -738,6 +738,10 @@ inline Real hz2hz(Real hz){ return hz; } +inline Real cents2hz(Real frequencyB, Real cents) { + return frequencyB * powf(2, cents/1200) +} + inline Real hz2cents(Real frequencyA, Real frequencyB) { return 1200 * log2(frequencyA / frequencyB); } From be9d709b4f735a661cefe5e0df2896d4e021efac Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Fri, 24 May 2024 16:47:26 +0200 Subject: [PATCH 08/23] Apply identation style --- src/essentia/essentiamath.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 20ce687e7..3f041dd5b 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -739,7 +739,7 @@ inline Real hz2hz(Real hz){ } inline Real cents2hz(Real frequencyB, Real cents) { - return frequencyB * powf(2, cents/1200) + return frequencyB * powf(2, cents/1200); } inline Real hz2cents(Real frequencyA, Real frequencyB) { @@ -747,31 +747,31 @@ inline Real hz2cents(Real frequencyA, Real frequencyB) { } inline int hz2midi(Real hz, Real tuningFrequency) { - return 69 + (int) round(log2(hz / tuningFrequency) * 12); + return 69 + (int) round(log2(hz / tuningFrequency) * 12); } inline std::string midi2note(int midiNoteNumber) { - const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; - int noteIdx = midiNoteNumber - 69; - int idx = abs(noteIdx) % 12; - int octave = 4 + floor((noteIdx + 9) / 12.0); - if (noteIdx < 0) { - idx = abs(idx - 12) % 12; - } - std::string closest_note = ALL_NOTES[idx] + std::to_string(octave); - return closest_note; + const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; + int noteIdx = midiNoteNumber - 69; + int idx = abs(noteIdx) % 12; + int octave = 4 + floor((noteIdx + 9) / 12.0); + if (noteIdx < 0) { + idx = abs(idx - 12) % 12; + } + std::string closest_note = ALL_NOTES[idx] + std::to_string(octave); + return closest_note; } inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { - return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); + return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); } inline int db2velocity (Real decibels, Real hearingThreshold) { - int velocity = 0; - if (decibels > hearingThreshold) { - velocity = (int)((hearingThreshold - decibels) * 127 / hearingThreshold); // decibels should be negative - } - return velocity; + int velocity = 0; + if (decibels > hearingThreshold) { + velocity = (int)((hearingThreshold - decibels) * 127 / hearingThreshold); // decibels should be negative + } + return velocity; } inline int argmin(const std::vector& input) { From 9f8cb11ba51309dfe24724768b611b7008d5166e Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Fri, 24 May 2024 16:48:45 +0200 Subject: [PATCH 09/23] Add default tuning frequency for midi2hz() and hz2midi() --- src/python/essentia/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index f942f511e..e114f15b5 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -63,11 +63,11 @@ def mel2hz(arg): def hz2mel(arg): return _essentia.hz2mel( _c.convertData(arg, _c.Edt.REAL) ) -def midi2hz(arg1, arg2): +def midi2hz(arg1, arg2=440.0): return _essentia.midi2hz( _c.convertData(arg1, _c.Edt.INTEGER), _c.convertData(arg2, _c.Edt.REAL) ) -def hz2midi(arg1, arg2): +def hz2midi(arg1, arg2=440.0): return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) From 4c522f9427540ec56391dfddb39bbf3ce1868315 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Fri, 24 May 2024 16:50:40 +0200 Subject: [PATCH 10/23] Add python binding and unittest for cents2hz() --- src/python/essentia/utils.py | 4 ++++ src/python/globalfuncs.cpp | 15 +++++++++++++++ test/src/unittests/base/test_utils.py | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index e114f15b5..0b956499c 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -71,6 +71,10 @@ def hz2midi(arg1, arg2=440.0): return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) +def cents2hz(arg1, arg2): + return _essentia.hz2cents(_c.convertData(arg1, _c.Edt.REAL), + _c.convertData(arg2, _c.Edt.REAL) ) + def hz2cents(arg1, arg2): return _essentia.hz2cents(_c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index 7144fbfed..ff4cdbedf 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -430,6 +430,20 @@ hzToCents(PyObject* notUsed, PyObject* args) { return PyFloat_FromDouble( int(cents) ); } +static PyObject* +centsToHz(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + + if (argsV.size() != 2 || !PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1])) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real frequencyB, Real cents)"); + return NULL; + } + + int hz = cents2hz( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); + return PyFloat_FromDouble( int(hz) ); +} + static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { if (!PyString_Check(arg)) { @@ -1044,6 +1058,7 @@ static PyMethodDef Essentia__Methods[] = { { "midi2hz", midiToHz, METH_VARARGS, "Converts a midi note number to frequency in Hz" }, { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, + { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a frequency in Hz and cents distance" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index ce6c76bb5..e49b6896b 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -128,6 +128,12 @@ def testHzToCents(self): expected_cents = 100 self.assertAlmostEqual(expected_cents, hz2cents(midi2hz(midi, tuning), tuning)) + def testCentsToHz(self): + tuning = 440 + cents = 100 + expected_hz = 466.163 + self.assertAlmostEqual(expected_hz, cents2hz(tuning, cents)) + suite = allTests(TestUtils) From 323cc44c9e1129bded61fc31b0698952264698f7 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 10:10:28 +0200 Subject: [PATCH 11/23] Small fix --- src/essentia/essentiamath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 3f041dd5b..8dc6566e8 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -739,7 +739,7 @@ inline Real hz2hz(Real hz){ } inline Real cents2hz(Real frequencyB, Real cents) { - return frequencyB * powf(2, cents/1200); + return frequencyB * powf(2.0, cents / 1200.0); } inline Real hz2cents(Real frequencyA, Real frequencyB) { From 3340cc94c60567ecc5955271f9564750b951e9e1 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 10:13:01 +0200 Subject: [PATCH 12/23] Fix cents2hz() and add midi2note() --- src/python/essentia/utils.py | 8 ++++++-- src/python/globalfuncs.cpp | 18 ++++++++++++++++-- test/src/unittests/base/test_utils.py | 7 ++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index 0b956499c..2c3653d4c 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -72,13 +72,16 @@ def hz2midi(arg1, arg2=440.0): _c.convertData(arg2, _c.Edt.REAL) ) def cents2hz(arg1, arg2): - return _essentia.hz2cents(_c.convertData(arg1, _c.Edt.REAL), + return _essentia.cents2hz(_c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) def hz2cents(arg1, arg2): return _essentia.hz2cents(_c.convertData(arg1, _c.Edt.REAL), _c.convertData(arg2, _c.Edt.REAL) ) +def midi2note(arg): + return _essentia.midi2note( _c.convertData(arg, _c.Edt.INTEGER) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -103,7 +106,8 @@ def derivative(array): 'bark2hz', 'hz2bark', 'mel2hz', 'hz2mel', 'midi2hz', 'hz2midi', - 'hz2cents', + 'cents2hz', 'hz2cents', + 'midi2note', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index ff4cdbedf..52460675c 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -440,8 +440,21 @@ centsToHz(PyObject* notUsed, PyObject* args) { return NULL; } - int hz = cents2hz( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); - return PyFloat_FromDouble( int(hz) ); + Real hz = cents2hz( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); + return PyFloat_FromDouble( hz ); +} + +static PyObject* +midiToNote(PyObject* notUsed, PyObject* arg) { + + if (!PyLong_Check(arg)) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (int midiNoteNumber)"); + return NULL; + } + + std::string note = midi2note( long( PyLong_AsLong(arg) ) ); + const char *c_note = note.c_str(); + return PyString_FromString( c_note ); } static PyObject* @@ -1059,6 +1072,7 @@ static PyMethodDef Essentia__Methods[] = { { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a frequency in Hz and cents distance" }, + { "midi2note", midiToNote, METH_O, "Converts a midi note number to note applying the international pitch standard (A4=440Hz)" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index e49b6896b..102edb877 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -131,9 +131,14 @@ def testHzToCents(self): def testCentsToHz(self): tuning = 440 cents = 100 - expected_hz = 466.163 + expected_hz = 466.16378 self.assertAlmostEqual(expected_hz, cents2hz(tuning, cents)) + def testMidiToNote(self): + midi = 69 + expected_note = "A4" + self.assertEqual(expected_note, midi2note(midi)) + suite = allTests(TestUtils) From c10dc6b2605138d4a9547657d6d5a2ea2647e0aa Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 11:17:53 +0200 Subject: [PATCH 13/23] Add note2root() and note2octave() --- src/essentia/essentiamath.h | 9 +++++++++ src/python/essentia/utils.py | 7 +++++++ src/python/globalfuncs.cpp | 27 +++++++++++++++++++++++++++ test/src/unittests/base/test_utils.py | 10 ++++++++++ 4 files changed, 53 insertions(+) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 8dc6566e8..ef5b0cfb6 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -762,6 +762,15 @@ inline std::string midi2note(int midiNoteNumber) { return closest_note; } +inline std::string note2root(std::string note) { + return note.substr(0, note.size()-1); +} + +inline int note2octave(std::string note) { + char octaveChar = note.back(); + return octaveChar - '0'; +} + inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); } diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index 2c3653d4c..279004dca 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -82,6 +82,12 @@ def hz2cents(arg1, arg2): def midi2note(arg): return _essentia.midi2note( _c.convertData(arg, _c.Edt.INTEGER) ) +def note2root(arg): + return _essentia.note2root( _c.convertData(arg, _c.Edt.STRING) ) + +def note2octave(arg): + return _essentia.note2octave( _c.convertData(arg, _c.Edt.STRING) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -107,6 +113,7 @@ def derivative(array): 'mel2hz', 'hz2mel', 'midi2hz', 'hz2midi', 'cents2hz', 'hz2cents', + 'note2root', 'note2octave', 'midi2note', 'postProcessTicks', 'normalize', 'derivative', diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index 52460675c..b166b30bf 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -457,6 +457,31 @@ midiToNote(PyObject* notUsed, PyObject* arg) { return PyString_FromString( c_note ); } +static PyObject* +noteToRoot(PyObject* notUsed, PyObject* arg) { + + if (!PyString_Check(arg)) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (string note)"); + return NULL; + } + + std::string root = note2root( PyString_AS_STRING(arg) ); + const char *c_root = root.c_str(); + return PyString_FromString( c_root ); +} + +static PyObject* +noteToOctave(PyObject* notUsed, PyObject* arg) { + + if (!PyString_Check(arg)) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (string note)"); + return NULL; + } + + int octave = note2octave( PyString_AS_STRING(arg) ); + return PyLong_FromLong( int(octave) ); +} + static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { if (!PyString_Check(arg)) { @@ -1073,6 +1098,8 @@ static PyMethodDef Essentia__Methods[] = { { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a frequency in Hz and cents distance" }, { "midi2note", midiToNote, METH_O, "Converts a midi note number to note applying the international pitch standard (A4=440Hz)" }, + { "note2root", noteToRoot, METH_O, "Returns the root of a note" }, + { "note2octave", noteToOctave, METH_O, "Returns the octave of a note" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 102edb877..a02f2bc9f 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -139,6 +139,16 @@ def testMidiToNote(self): expected_note = "A4" self.assertEqual(expected_note, midi2note(midi)) + def testNoteToRoot(self): + note = "A4" + expected_root = note[0] + self.assertEqual(expected_root, note2root(note)) + + def testNoteToOctave(self): + note = "A4" + expected_octave = int(note[1]) + self.assertEqual(expected_octave, note2octave(note)) + suite = allTests(TestUtils) From c84a9a504938635decadbd7bdd8c254051834977 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 12:56:58 +0200 Subject: [PATCH 14/23] Add note2midi() math function --- src/essentia/essentiamath.h | 44 ++++++++++++++++++++------- src/python/essentia/utils.py | 5 ++- src/python/globalfuncs.cpp | 13 ++++++++ test/src/unittests/base/test_utils.py | 11 +++++++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index ef5b0cfb6..dcc68a65b 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -750,6 +750,19 @@ inline int hz2midi(Real hz, Real tuningFrequency) { return 69 + (int) round(log2(hz / tuningFrequency) * 12); } +inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { + return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); +} + +inline std::string note2root(std::string note) { + return note.substr(0, note.size()-1); +} + +inline int note2octave(std::string note) { + char octaveChar = note.back(); + return octaveChar - '0'; +} + inline std::string midi2note(int midiNoteNumber) { const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; int noteIdx = midiNoteNumber - 69; @@ -762,17 +775,26 @@ inline std::string midi2note(int midiNoteNumber) { return closest_note; } -inline std::string note2root(std::string note) { - return note.substr(0, note.size()-1); -} - -inline int note2octave(std::string note) { - char octaveChar = note.back(); - return octaveChar - '0'; -} - -inline Real midi2hz(int midiNoteNumber, Real tuningFrequency) { - return tuningFrequency * powf(2, (midiNoteNumber - 69) / 12.0); +inline int note2midi(std::string note) { + const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; + int octave = note2octave(note); + std::string root = note2root(note); + int nNotes = ALL_NOTES.size(); + int CIdx = 3; + + int noteIdx = floor((octave - (CIdx + 1)) * nNotes); + int idx = 0; + for (int i = 0; i < nNotes; i++) { + if (ALL_NOTES[i] == root) { + idx = i; + if (idx >= CIdx) { + idx = idx - nNotes; + } + i = nNotes; + } + } + int midiNote = noteIdx + 69 + idx; + return midiNote; } inline int db2velocity (Real decibels, Real hearingThreshold) { diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index 279004dca..d60780c2f 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -82,6 +82,9 @@ def hz2cents(arg1, arg2): def midi2note(arg): return _essentia.midi2note( _c.convertData(arg, _c.Edt.INTEGER) ) +def note2midi(arg): + return _essentia.note2midi( _c.convertData(arg, _c.Edt.STRING) ) + def note2root(arg): return _essentia.note2root( _c.convertData(arg, _c.Edt.STRING) ) @@ -114,7 +117,7 @@ def derivative(array): 'midi2hz', 'hz2midi', 'cents2hz', 'hz2cents', 'note2root', 'note2octave', - 'midi2note', + 'midi2note', 'note2midi', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index b166b30bf..063ae0c3a 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -457,6 +457,18 @@ midiToNote(PyObject* notUsed, PyObject* arg) { return PyString_FromString( c_note ); } +static PyObject* +noteToMidi(PyObject* notUsed, PyObject* arg) { + + if (!PyString_Check(arg)) { + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (string note)"); + return NULL; + } + + int octave = note2midi( PyString_AS_STRING(arg) ); + return PyLong_FromLong( int(octave) ); +} + static PyObject* noteToRoot(PyObject* notUsed, PyObject* arg) { @@ -1098,6 +1110,7 @@ static PyMethodDef Essentia__Methods[] = { { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a frequency in Hz and cents distance" }, { "midi2note", midiToNote, METH_O, "Converts a midi note number to note applying the international pitch standard (A4=440Hz)" }, + { "note2midi", noteToMidi, METH_O, "Converts note (applying the international pitch standard A4=440Hz) to midi note number" }, { "note2root", noteToRoot, METH_O, "Returns the root of a note" }, { "note2octave", noteToOctave, METH_O, "Returns the octave of a note" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index a02f2bc9f..04cb2bf4b 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -139,6 +139,17 @@ def testMidiToNote(self): expected_note = "A4" self.assertEqual(expected_note, midi2note(midi)) + def testNoteToMidi(self): + note = "A4" + expected_midi = 69 + self.assertEqual(expected_midi, note2midi(note)) + note = "C4" + expected_midi = 60 + self.assertEqual(expected_midi, note2midi(note)) + note = "C5" + expected_midi = 72 + self.assertEqual(expected_midi, note2midi(note)) + def testNoteToRoot(self): note = "A4" expected_root = note[0] From a06268ade76ae24251acb8ff544849f15d13e40a Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 13:01:51 +0200 Subject: [PATCH 15/23] Small refactor --- src/essentia/essentiamath.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index dcc68a65b..3ed7474ef 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -765,11 +765,14 @@ inline int note2octave(std::string note) { inline std::string midi2note(int midiNoteNumber) { const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; + int nNotes = ALL_NOTES.size(); + int CIdx = 3; + int diffCIdx = nNotes - CIdx; int noteIdx = midiNoteNumber - 69; - int idx = abs(noteIdx) % 12; - int octave = 4 + floor((noteIdx + 9) / 12.0); + int idx = abs(noteIdx) % nNotes; + int octave = (CIdx + 1) + floor((noteIdx + diffCIdx) / nNotes); if (noteIdx < 0) { - idx = abs(idx - 12) % 12; + idx = abs(idx - nNotes) % nNotes; } std::string closest_note = ALL_NOTES[idx] + std::to_string(octave); return closest_note; From c50c337f0ddf9e180ff55fb9f467d90269e1f816 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 13:32:53 +0200 Subject: [PATCH 16/23] Add hz2note() and note2hz() math functions --- src/essentia/essentiamath.h | 10 +++++++++ src/python/essentia/utils.py | 9 ++++++++ src/python/globalfuncs.cpp | 31 +++++++++++++++++++++++++++ test/src/unittests/base/test_utils.py | 10 +++++++++ 4 files changed, 60 insertions(+) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 3ed7474ef..0ee4395cb 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -800,6 +800,16 @@ inline int note2midi(std::string note) { return midiNote; } +inline std::string hz2note(Real hz, Real tuningFrequency) { + int midiNoteNumber = hz2midi(hz, tuningFrequency); + return midi2note(midiNoteNumber); +} + +inline int note2hz(std::string note, Real tuningFrequency) { + int midiNoteNumber = note2midi(note); + return midi2hz(midiNoteNumber, tuningFrequency); +} + inline int db2velocity (Real decibels, Real hearingThreshold) { int velocity = 0; if (decibels > hearingThreshold) { diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index d60780c2f..56c04948c 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -91,6 +91,14 @@ def note2root(arg): def note2octave(arg): return _essentia.note2octave( _c.convertData(arg, _c.Edt.STRING) ) +def hz2note(arg1, arg2=440.0): + return _essentia.hz2note( _c.convertData(arg1, _c.Edt.REAL), + _c.convertData(arg2, _c.Edt.REAL) ) + +def note2hz(arg1, arg2=440.0): + return _essentia.note2hz( _c.convertData(arg1, _c.Edt.STRING), + _c.convertData(arg2, _c.Edt.REAL) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -118,6 +126,7 @@ def derivative(array): 'cents2hz', 'hz2cents', 'note2root', 'note2octave', 'midi2note', 'note2midi', + 'hz2note', 'note2hz', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index 063ae0c3a..52fbc6485 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -494,6 +494,35 @@ noteToOctave(PyObject* notUsed, PyObject* arg) { return PyLong_FromLong( int(octave) ); } +static PyObject* +hzToNote(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (Real hertz, Real tuningFrequency)"); + return NULL; + } + + std::string note = hz2note( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + const char *c_note = note.c_str(); + return PyString_FromString( c_note ); +} + +static PyObject* +noteToHz(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyString_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (string note, Real tuningFrequency)"); + return NULL; + } + + Real hz = note2hz( PyString_AS_STRING(argsV[0]), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + return PyFloat_FromDouble( hz ); +} + static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { if (!PyString_Check(arg)) { @@ -1113,6 +1142,8 @@ static PyMethodDef Essentia__Methods[] = { { "note2midi", noteToMidi, METH_O, "Converts note (applying the international pitch standard A4=440Hz) to midi note number" }, { "note2root", noteToRoot, METH_O, "Returns the root of a note" }, { "note2octave", noteToOctave, METH_O, "Returns the octave of a note" }, + { "hz2note", hzToNote, METH_VARARGS, "Converts a frequency in Hz to a note - applying the international pitch standard A4=440Hz" }, + { "note2hz", noteToHz, METH_VARARGS, "Converts a note - applying the international pitch standard A4=440Hz - into a frequency in Hz" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 04cb2bf4b..6c9c477c4 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -160,6 +160,16 @@ def testNoteToOctave(self): expected_octave = int(note[1]) self.assertEqual(expected_octave, note2octave(note)) + def testHzToNote(self): + hz = 440 + expected_note = "A4" + self.assertEqual(expected_note, hz2note(hz)) + + def testNoteToHz(self): + note = "A4" + expected_hz = 440 + self.assertEqual(expected_hz, note2hz(note)) + suite = allTests(TestUtils) From ed89c21bb8805f95dedf91cb18560eb6b850f056 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Mon, 27 May 2024 13:55:01 +0200 Subject: [PATCH 17/23] Add velocity2db() and db2velocity() as math functions --- src/essentia/essentiamath.h | 4 ++++ src/python/essentia/utils.py | 9 +++++++ src/python/globalfuncs.cpp | 34 +++++++++++++++++++++++++-- test/src/unittests/base/test_utils.py | 16 +++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 0ee4395cb..2f6ba81c0 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -818,6 +818,10 @@ inline int db2velocity (Real decibels, Real hearingThreshold) { return velocity; } +inline Real velocity2db(int velocity, Real hearingThreshold) { + return -(hearingThreshold * velocity / 127 -hearingThreshold); +} + inline int argmin(const std::vector& input) { if (input.empty()) throw EssentiaException("trying to get argmin of empty array"); diff --git a/src/python/essentia/utils.py b/src/python/essentia/utils.py index 56c04948c..d9e01fff8 100644 --- a/src/python/essentia/utils.py +++ b/src/python/essentia/utils.py @@ -99,6 +99,14 @@ def note2hz(arg1, arg2=440.0): return _essentia.note2hz( _c.convertData(arg1, _c.Edt.STRING), _c.convertData(arg2, _c.Edt.REAL) ) +def velocity2db(arg1, arg2=-96): + return _essentia.velocity2db( _c.convertData(arg1, _c.Edt.INTEGER), + _c.convertData(arg2, _c.Edt.REAL) ) + +def db2velocity(arg1, arg2=-96): + return _essentia.db2velocity( _c.convertData(arg1, _c.Edt.REAL), + _c.convertData(arg2, _c.Edt.REAL) ) + def equivalentKey(arg): return _essentia.equivalentKey( _c.convertData(arg, _c.Edt.STRING) ) @@ -127,6 +135,7 @@ def derivative(array): 'note2root', 'note2octave', 'midi2note', 'note2midi', 'hz2note', 'note2hz', + 'velocity2db', 'db2velocity', 'postProcessTicks', 'normalize', 'derivative', 'equivalentKey', 'lin2log'] diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index 52fbc6485..ac419ff7f 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -504,7 +504,7 @@ hzToNote(PyObject* notUsed, PyObject* args) { return NULL; } - std::string note = hz2note( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + std::string note = hz2note( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); const char *c_note = note.c_str(); return PyString_FromString( c_note ); } @@ -519,10 +519,38 @@ noteToHz(PyObject* notUsed, PyObject* args) { return NULL; } - Real hz = note2hz( PyString_AS_STRING(argsV[0]), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + Real hz = note2hz( PyString_AS_STRING(argsV[0]), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); return PyFloat_FromDouble( hz ); } +static PyObject* +velocityToDb(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyLong_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (int velocity, Real hearingThreshold)"); + return NULL; + } + + Real db = velocity2db( long( PyLong_AsLong(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) ); + return PyFloat_FromDouble( db ); +} + +static PyObject* +dbToVelocity(PyObject* notUsed, PyObject* args) { + // parse args to get Source alg, name and source alg and source name + vector argsV = unpack(args); + if (argsV.size() != 2 || + (!PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1]))) { + PyErr_SetString(PyExc_ValueError, "expecting arguments (Real decibels, Real hearingThreshold)"); + return NULL; + } + + long velocity = db2velocity( Real( PyFloat_AS_DOUBLE(argsV[0])), Real( PyFloat_AS_DOUBLE(argsV[1])) ); + return PyLong_FromLong( int(velocity) ); +} + static PyObject* getEquivalentKey(PyObject* notUsed, PyObject* arg) { if (!PyString_Check(arg)) { @@ -1144,6 +1172,8 @@ static PyMethodDef Essentia__Methods[] = { { "note2octave", noteToOctave, METH_O, "Returns the octave of a note" }, { "hz2note", hzToNote, METH_VARARGS, "Converts a frequency in Hz to a note - applying the international pitch standard A4=440Hz" }, { "note2hz", noteToHz, METH_VARARGS, "Converts a note - applying the international pitch standard A4=440Hz - into a frequency in Hz" }, + { "velocity2db", velocityToDb, METH_VARARGS, "Converts a velocity to a measure in dB" }, + { "db2velocity", dbToVelocity, METH_VARARGS, "Converts a dB measure of power to velocity [0-127]" }, { "lin2db", linToDb, METH_O, "Converts a linear measure of power to a measure in dB" }, { "db2lin", dbToLin, METH_O, "Converts a dB measure of power to a linear measure" }, { "db2pow", dbToPow, METH_O, "Converts a dB measure of power to a linear measure" }, diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index 6c9c477c4..c31a4d497 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -170,6 +170,22 @@ def testNoteToHz(self): expected_hz = 440 self.assertEqual(expected_hz, note2hz(note)) + def testDbToVelocity(self): + decibels = 0 + expected_velocity = 127 + self.assertEqual(expected_velocity, db2velocity(decibels)) + decibels = -96 + expected_velocity = 0 + self.assertEqual(expected_velocity, db2velocity(decibels)) + + def testVelocityToDb(self): + velocity = 127 + expected_decibels = 0 + self.assertEqual(expected_decibels, velocity2db(velocity)) + velocity = 0 + expected_decibels = -96 + self.assertEqual(expected_decibels, velocity2db(velocity)) + suite = allTests(TestUtils) From 4d2acbe605a7bef5ba3ad5d017425455f563707d Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Tue, 28 May 2024 12:16:16 +0200 Subject: [PATCH 18/23] Change the frequencyB variable by referenceFrequency in cents2hz() and hz2cents() --- src/essentia/essentiamath.h | 8 ++++---- src/python/globalfuncs.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 2f6ba81c0..72072a6c7 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -738,12 +738,12 @@ inline Real hz2hz(Real hz){ return hz; } -inline Real cents2hz(Real frequencyB, Real cents) { - return frequencyB * powf(2.0, cents / 1200.0); +inline Real cents2hz(Real cents, Real referenceFrequency) { + return referenceFrequency * powf(2.0, cents / 1200.0); } -inline Real hz2cents(Real frequencyA, Real frequencyB) { - return 1200 * log2(frequencyA / frequencyB); +inline Real hz2cents(Real hz, Real referenceFrequency) { + return 1200 * log2(hz / referenceFrequency); } inline int hz2midi(Real hz, Real tuningFrequency) { diff --git a/src/python/globalfuncs.cpp b/src/python/globalfuncs.cpp index ac419ff7f..413abe32a 100644 --- a/src/python/globalfuncs.cpp +++ b/src/python/globalfuncs.cpp @@ -422,7 +422,7 @@ hzToCents(PyObject* notUsed, PyObject* args) { vector argsV = unpack(args); if (argsV.size() != 2 || !PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1])) { - PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real frequencyA, Real frequencyB)"); + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real hertz, Real referenceFrequency)"); return NULL; } @@ -436,7 +436,7 @@ centsToHz(PyObject* notUsed, PyObject* args) { vector argsV = unpack(args); if (argsV.size() != 2 || !PyFloat_Check(argsV[0]) || !PyFloat_Check(argsV[1])) { - PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real frequencyB, Real cents)"); + PyErr_SetString(PyExc_TypeError, (char*)"expecting arguments (Real cents, Real referenceFrequency)"); return NULL; } @@ -1164,8 +1164,8 @@ static PyMethodDef Essentia__Methods[] = { { "hz2mel", hzToMel, METH_O, "Converts a frequency in Hz to a mel band" }, { "midi2hz", midiToHz, METH_VARARGS, "Converts a midi note number to frequency in Hz" }, { "hz2midi", hzToMidi, METH_VARARGS, "Converts a frequency in Hz to a midi note number" }, - { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" }, - { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a frequency in Hz and cents distance" }, + { "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between a frequency and a reference frequency in Hz" }, + { "cents2hz", centsToHz, METH_VARARGS, "Returns the frequency from a cents distance [0-1200] and a reference frequency in Hz" }, { "midi2note", midiToNote, METH_O, "Converts a midi note number to note applying the international pitch standard (A4=440Hz)" }, { "note2midi", noteToMidi, METH_O, "Converts note (applying the international pitch standard A4=440Hz) to midi note number" }, { "note2root", noteToRoot, METH_O, "Returns the root of a note" }, From 55755280b92dce8b50101b3ebe881cb994830a3a Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Tue, 28 May 2024 12:21:29 +0200 Subject: [PATCH 19/23] Break out the loop in explicit way. --- src/essentia/essentiamath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 72072a6c7..9ec9b05af 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -793,7 +793,7 @@ inline int note2midi(std::string note) { if (idx >= CIdx) { idx = idx - nNotes; } - i = nNotes; + break; } } int midiNote = noteIdx + 69 + idx; From 5dc241425918149da762041ad8be2ccfee2b3d61 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Tue, 28 May 2024 12:32:26 +0200 Subject: [PATCH 20/23] Apply identation style --- src/essentia/essentiamath.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index 9ec9b05af..d5d947d49 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -788,13 +788,13 @@ inline int note2midi(std::string note) { int noteIdx = floor((octave - (CIdx + 1)) * nNotes); int idx = 0; for (int i = 0; i < nNotes; i++) { - if (ALL_NOTES[i] == root) { - idx = i; - if (idx >= CIdx) { - idx = idx - nNotes; - } - break; + if (ALL_NOTES[i] == root) { + idx = i; + if (idx >= CIdx) { + idx = idx - nNotes; } + break; + } } int midiNote = noteIdx + 69 + idx; return midiNote; From 1e09fd7d3ddc23c2fc9de30016b26fd7f4f63a3e Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Tue, 28 May 2024 12:40:24 +0200 Subject: [PATCH 21/23] Change the input sorting in testCentsToHz() --- test/src/unittests/base/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/unittests/base/test_utils.py b/test/src/unittests/base/test_utils.py index c31a4d497..25d667455 100644 --- a/test/src/unittests/base/test_utils.py +++ b/test/src/unittests/base/test_utils.py @@ -132,7 +132,7 @@ def testCentsToHz(self): tuning = 440 cents = 100 expected_hz = 466.16378 - self.assertAlmostEqual(expected_hz, cents2hz(tuning, cents)) + self.assertAlmostEqual(expected_hz, cents2hz(cents, tuning)) def testMidiToNote(self): midi = 69 From 9b383ab9b108b5e7c6a4818b4db867714d256db2 Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 29 May 2024 12:33:06 +0200 Subject: [PATCH 22/23] Add ALL_NOTES as a macro --- src/essentia/essentiamath.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/essentia/essentiamath.h b/src/essentia/essentiamath.h index d5d947d49..3b8d06da4 100644 --- a/src/essentia/essentiamath.h +++ b/src/essentia/essentiamath.h @@ -39,6 +39,7 @@ #include "utils/tnt/tnt2essentiautils.h" #define M_2PI (2 * M_PI) +#define ALL_NOTES "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" namespace essentia { @@ -764,8 +765,9 @@ inline int note2octave(std::string note) { } inline std::string midi2note(int midiNoteNumber) { - const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; - int nNotes = ALL_NOTES.size(); + std::string NOTES[] = {ALL_NOTES}; + //int nNotes = NOTES.size(); + int nNotes = *(&NOTES + 1) - NOTES; int CIdx = 3; int diffCIdx = nNotes - CIdx; int noteIdx = midiNoteNumber - 69; @@ -774,21 +776,23 @@ inline std::string midi2note(int midiNoteNumber) { if (noteIdx < 0) { idx = abs(idx - nNotes) % nNotes; } - std::string closest_note = ALL_NOTES[idx] + std::to_string(octave); + std::string closest_note = NOTES[idx] + std::to_string(octave); return closest_note; } inline int note2midi(std::string note) { - const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; + //const std::vector ALL_NOTES { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" }; + std::string NOTES[] = {ALL_NOTES}; int octave = note2octave(note); std::string root = note2root(note); - int nNotes = ALL_NOTES.size(); + int nNotes = *(&NOTES + 1) - NOTES; + //int nNotes = NOTES.size(); int CIdx = 3; int noteIdx = floor((octave - (CIdx + 1)) * nNotes); int idx = 0; for (int i = 0; i < nNotes; i++) { - if (ALL_NOTES[i] == root) { + if (NOTES[i] == root) { idx = i; if (idx >= CIdx) { idx = idx - nNotes; From 1e6b6921d7a9b164a221c0a069d5d5aec460ddbf Mon Sep 17 00:00:00 2001 From: Xavier Lizarraga Date: Wed, 29 May 2024 14:11:05 +0200 Subject: [PATCH 23/23] Fix issue with tuning frequency estimating midi notes. --- src/algorithms/tonal/pitchyinprobabilities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/tonal/pitchyinprobabilities.cpp b/src/algorithms/tonal/pitchyinprobabilities.cpp index 7518ff6d4..d97ce805c 100644 --- a/src/algorithms/tonal/pitchyinprobabilities.cpp +++ b/src/algorithms/tonal/pitchyinprobabilities.cpp @@ -321,7 +321,7 @@ void PitchYinProbabilities::compute() { bool isLowAmplitude = (RMS < _lowAmp); for (size_t iCandidate = 0; iCandidate < _freq.size(); ++iCandidate) { - Real pitchCents = hz2midi(_freq[iCandidate]); + Real pitchCents = hz2midi(_freq[iCandidate], 440.0); _freq[iCandidate] = pitchCents; if (isLowAmplitude) { // lower the probabilities of the frequencies by calculating the weighted sum