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

Add essentiamath #1417

Merged
merged 23 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e7544ae
Add hz2midi, midi2hz, hz2cents, midi2note and db2velocity in essentia…
xaviliz May 22, 2024
0813303
Add python binding and unittest for hz2midi()
xaviliz May 22, 2024
9bbc41d
Add unittest for midi2hz()
xaviliz May 22, 2024
905fd16
Replace old hz2cents() by hz2midi()
xaviliz May 22, 2024
4b061bf
Add hz2cents unittest
xaviliz May 23, 2024
b426754
Harcode all_notes vector in midi2note()
xaviliz May 24, 2024
2191b6d
Add cents2hz
xaviliz May 24, 2024
be9d709
Apply identation style
xaviliz May 24, 2024
9f8cb11
Add default tuning frequency for midi2hz() and hz2midi()
xaviliz May 24, 2024
4c522f9
Add python binding and unittest for cents2hz()
xaviliz May 24, 2024
323cc44
Small fix
xaviliz May 27, 2024
3340cc9
Fix cents2hz() and add midi2note()
xaviliz May 27, 2024
c10dc6b
Add note2root() and note2octave()
xaviliz May 27, 2024
c84a9a5
Add note2midi() math function
xaviliz May 27, 2024
a06268a
Small refactor
xaviliz May 27, 2024
c50c337
Add hz2note() and note2hz() math functions
xaviliz May 27, 2024
ed89c21
Add velocity2db() and db2velocity() as math functions
xaviliz May 27, 2024
4d2acbe
Change the frequencyB variable by referenceFrequency in cents2hz() an…
xaviliz May 28, 2024
5575528
Break out the loop in explicit way.
xaviliz May 28, 2024
5dc2414
Apply identation style
xaviliz May 28, 2024
1e09fd7
Change the input sorting in testCentsToHz()
xaviliz May 28, 2024
9b383ab
Add ALL_NOTES as a macro
xaviliz May 29, 2024
1e6b692
Fix issue with tuning frequency estimating midi notes.
xaviliz May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/algorithms/tonal/pitchyinprobabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
84 changes: 82 additions & 2 deletions src/essentia/essentiamath.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,8 +738,88 @@ inline Real hz2hz(Real hz){
return hz;
}

inline Real hz2cents(Real hz) {
return 12 * std::log(hz/440)/std::log(2.) + 69;
inline Real cents2hz(Real frequencyB, Real cents) {
return frequencyB * powf(2.0, cents / 1200.0);
}

inline Real hz2cents(Real frequencyA, Real frequencyB) {
return 1200 * log2(frequencyA / frequencyB);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frequencyA vs frequencyB can be unclear to users. Maybe we could use a different naming for frequencyB like referenceFrequency?

Copy link
Contributor Author

@xaviliz xaviliz May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that makes sense. I doubted with tuningFrequency but referenceFrequency works better. Thanks.

}

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<std::string> 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) % nNotes;
int octave = (CIdx + 1) + floor((noteIdx + diffCIdx) / nNotes);
if (noteIdx < 0) {
idx = abs(idx - nNotes) % nNotes;
}
std::string closest_note = ALL_NOTES[idx] + std::to_string(octave);
return closest_note;
}

inline int note2midi(std::string note) {
const std::vector<std::string> 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break out of the loop once the matching note is found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L796 is doing it by index. However it can be replaced by a break to make it more explicit. Thanks again to point it out.

idx = i;
if (idx >= CIdx) {
idx = idx - nNotes;
}
i = nNotes;
}
}
int midiNote = noteIdx + 69 + idx;
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) {
velocity = (int)((hearingThreshold - decibels) * 127 / hearingThreshold); // decibels should be negative
}
return velocity;
}

inline Real velocity2db(int velocity, Real hearingThreshold) {
return -(hearingThreshold * velocity / 127 -hearingThreshold);
}

inline int argmin(const std::vector<Real>& input) {
Expand Down
50 changes: 50 additions & 0 deletions src/python/essentia/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,50 @@ def mel2hz(arg):
def hz2mel(arg):
return _essentia.hz2mel( _c.convertData(arg, _c.Edt.REAL) )

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=440.0):
return _essentia.hz2midi( _c.convertData(arg1, _c.Edt.REAL),
_c.convertData(arg2, _c.Edt.REAL) )

def cents2hz(arg1, arg2):
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 note2midi(arg):
return _essentia.note2midi( _c.convertData(arg, _c.Edt.STRING) )

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 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 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) )

Expand All @@ -86,6 +130,12 @@ def derivative(array):
'amp2db', 'db2amp',
'bark2hz', 'hz2bark',
'mel2hz', 'hz2mel',
'midi2hz', 'hz2midi',
'cents2hz', 'hz2cents',
'note2root', 'note2octave',
'midi2note', 'note2midi',
'hz2note', 'note2hz',
'velocity2db', 'db2velocity',
'postProcessTicks',
'normalize', 'derivative',
'equivalentKey', 'lin2log']
173 changes: 173 additions & 0 deletions src/python/globalfuncs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,167 @@ 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<PyObject*> 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
vector<PyObject*> 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*
hzToCents(PyObject* notUsed, PyObject* args) {
// parse args to get Source alg, name and source alg and source name
vector<PyObject*> 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)");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here

return NULL;
}

int cents = hz2cents( Real( PyFloat_AS_DOUBLE(argsV[0]) ), Real( PyFloat_AS_DOUBLE(argsV[1]) ) );
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<PyObject*> 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)");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

return NULL;
}

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*
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) {

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*
hzToNote(PyObject* notUsed, PyObject* args) {
// parse args to get Source alg, name and source alg and source name
vector<PyObject*> 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<PyObject*> 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*
velocityToDb(PyObject* notUsed, PyObject* args) {
// parse args to get Source alg, name and source alg and source name
vector<PyObject*> 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<PyObject*> 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) {
Expand Down Expand Up @@ -1001,6 +1162,18 @@ 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" },
{ "hz2cents", hzToCents, METH_VARARGS, "Returns the cents distance between two frequencies in Hz" },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use the referenceFrequency (see my comment about), we should updates this description too.

{ "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" },
{ "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" },
Expand Down
Loading
Loading