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

Pitchyinfft params #1399

Closed
wants to merge 9 commits into from
89 changes: 89 additions & 0 deletions src/algorithms/standard/framebuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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/
*/

#include "framebuffer.h"
//#include <cmath>
#include <algorithm>

using namespace std;
using namespace essentia;
using namespace standard;


const char* FrameBuffer::name = "FrameBuffer";
const char* FrameBuffer::category = "Standard";
const char* FrameBuffer::description = DOC(
"This algorithm buffers input non-overlapping audio frames into longer overlapping frames with a hop sizes equal to input frame size.\n\n"
"In standard mode, each compute() call updates and outputs the gathered buffer.\n\n"
"Input frames can be of variate length. Input frames longer than the buffer size will be cropped. Empty input frames will raise an exception."
);


void FrameBuffer::configure() {
_bufferSize = parameter("bufferSize").toInt();
_zeroPadding = parameter("zeroPadding").toBool();
_buffer.resize(_bufferSize);
reset();
}

void FrameBuffer::reset() {
if (_zeroPadding) {
std::fill(_buffer.begin(), _buffer.end(), (Real) 0.);
_bufferUndefined = 0;
}
else {
_bufferUndefined = _bufferSize;
}
}

void FrameBuffer::compute() {
const vector<Real>& frame = _frame.get();
vector<Real>& bufferedFrame = _bufferedFrame.get();

if (frame.empty()) throw EssentiaException("FrameBuffer: the input frame is empty");

int shift = (int) frame.size();

if (shift >= _bufferSize) {
// Overwrite the entire buffer.
std::copy(frame.end() - _bufferSize, frame.end(), _buffer.begin());
_bufferUndefined = 0;
// TODO E_WARNING for the case of shift > _bufferSize (not all input values fit the buffer)
}
else {
std::copy(_buffer.begin() + shift, _buffer.end(), _buffer.begin());
std::copy(frame.begin(), frame.end(), _buffer.begin() + _bufferSize - shift);
if (_bufferUndefined) {
_bufferUndefined -= shift;
if (_bufferUndefined < 0) {
_bufferUndefined = 0;
}
}
}

// output
if (!_bufferUndefined) {
bufferedFrame.resize(_bufferSize);
std::copy(_buffer.begin(), _buffer.end(), bufferedFrame.begin());
}
else {
// Return emtpy frames until a full buffer is available.
bufferedFrame.clear();
}
}
86 changes: 86 additions & 0 deletions src/algorithms/standard/framebuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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/
*/

#ifndef ESSENTIA_FRAMEBUFFER_H
#define ESSENTIA_FRAMEBUFFER_H

#include "algorithm.h"

namespace essentia {
namespace standard {

class FrameBuffer : public Algorithm {

private:
Input<std::vector<Real> > _frame;
Output<std::vector<Real> > _bufferedFrame;

std::vector<Real> _buffer;
int _bufferSize;
bool _zeroPadding;
int _bufferUndefined; // Number of undefined values in the buffer (= buffer size for the empty buffer on reset).

public:
FrameBuffer() {
declareInput(_frame, "frame", "the input audio frame");
declareOutput(_bufferedFrame, "frame", "the buffered audio frame");
}

void declareParameters() {
declareParameter("bufferSize", "the buffer size", "(0,inf)", 2048);
declareParameter("zeroPadding", "initialize the buffer with zeros (output zero-padded buffer frames if `true`, otherwise output empty frames until a full buffer is accumulated)", "{true,false}", true);
}
void compute();
void configure();
void reset();

static const char* name;
static const char* category;
static const char* description;

};

} // namespace standard
} // namespace essentia


#include "streamingalgorithmwrapper.h"

namespace essentia {
namespace streaming {

class FrameBuffer : public StreamingAlgorithmWrapper {

protected:

Sink<std::vector<Real> > _frame;
Source<std::vector<Real> > _bufferedFrame;

public:
FrameBuffer() {
declareAlgorithm("FrameBuffer");
declareInput(_frame, TOKEN,"frame");
declareOutput(_bufferedFrame, TOKEN, "frame");
}
};

} // namespace streaming
} // namespace essentia

#endif // ESSENTIA_FRAMEBUFFER_H
69 changes: 63 additions & 6 deletions src/algorithms/tonal/pitchyinfft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,38 @@ static const Real _freqsMask[] = {0., 20., 25., 31.5, 40., 50., 63., 80.,
1600., 2000., 2500., 3150., 4000., 5000., 6300., 8000., 9000., 10000.,
12500., 15000., 20000., 25100};

static const Real _weightMask[] = {-75.8, -70.1, -60.8, -52.1, -44.2, -37.5,
static Real _weightMask[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

static const Real _weights[] = {-75.8, -70.1, -60.8, -52.1, -44.2, -37.5,
-31.3, -25.6, -20.9, -16.5, -12.6, -9.6, -7.0, -4.7, -3.0, -1.8, -0.8,
-0.2, -0.0, 0.5, 1.6, 3.2, 5.4, 7.8, 8.1, 5.3, -2.4, -11.1, -12.8,
-12.2, -7.4, -17.8, -17.8, -17.8};
-12.2, -7.4, -17.8, -17.8, -17.8}; // by default the original one is selected

static const Real _aWeighting[] = {-148.6, -50.4, -44.8, -39.5, -34.5, -30.3,
-26.2, -22.4, -19.1, -16.2, -13.2, -10.8, -8.7, -6.6, -4.8, -3.2, -1.9,
-0.8, 0.0, 0.6, 1.0, 1.2, 1.3, 1.2, 1.0, 0.6, -0.1, -1.1, -1.8, -2.5,
-4.3, -6.0, -9.3, -12.4};

static const Real _bWeighting[] = {-96.4, -24.2, -20.5, -17.1, -14.1, -11.6,
-9.4, -7.3, -5.6, -4.2, -2.9, -2.0, -1.4, -0.9, -0.5, -0.3, -0.1, -0.0,
0.0, 0.0, -0.0, -0.1, -0.2, -0.4, -0.7, -1.2, -1.9, -2.9, -3.6, -4.3,
-6.1, -7.8, -11.2, -14.2};

static const Real _cWeighting[] = {-52.5, -6.2, -4.4, -3.0, -2.0, -1.3, -0.8,
-0.5, -0.3, -0.2, -0.1, -0.0, -0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.0,
-0.1, -0.2, -0.3, -0.5, -0.8, -1.3, -2.0, -3.0, -3.7, -4.4, -6.2,
-7.9, -11.3, -14.3};

static const Real _dWeighting[] = {-46.6, -20.6, -18.7, -16.7, -14.7, -12.8,
-10.9, -8.9, -7.2, -5.6, -3.9, -2.6, -1.6, -0.8, -0.4, -0.3, -0.5, -0.6,
0.0, 1.9, 5.0, 7.9, 10.3, 11.5, 11.1, 9.6, 7.6, 5.5, 4.4, 3.4, 1.4,
-0.2, -2.7, -4.7};

static const Real _zWeighting[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

const char* PitchYinFFT::name = "PitchYinFFT";
const char* PitchYinFFT::category = "Pitch";
Expand All @@ -56,13 +84,19 @@ void PitchYinFFT::configure() {
_sampleRate = parameter("sampleRate").toReal();
_interpolate = parameter("interpolate").toBool();
_tolerance = parameter("tolerance").toReal();
_weighting = parameter("weighting").toString();
_sqrMag.resize(_frameSize);
_weight.resize(_frameSize/2+1);
_yin.resize(_frameSize/2+1);
// configure algorithms
_fft->configure("size", _frameSize);

if (_weighting != "default" && _weighting != "A" && _weighting != "B" && _weighting != "C" && _weighting != "D" && _weighting != "Z") {
E_INFO("PitchYinFFT: 'weighting' = "<<_weighting<<"\n");
throw EssentiaException("PitchYinFFT: Bad 'weighting' parameter");
}
// allocate memory
spectralWeights();
spectralWeights(_weighting);

_tauMax = min(int(ceil(_sampleRate / parameter("minFrequency").toReal())), _frameSize/2);
_tauMin = min(int(floor(_sampleRate / parameter("maxFrequency").toReal())), _frameSize/2);
Expand All @@ -80,9 +114,31 @@ void PitchYinFFT::configure() {
"orderBy", "amplitude");
}

void PitchYinFFT::spectralWeights() {
void PitchYinFFT::spectralWeights(std::string weighting) {
int i = 0, j = 1;
Real freq = 0, a0 = 0, a1 = 0, f0 = 0, f1 = 0;
int _maskSize = 34;
if (weighting == "default") {
for (int n=0; n<_maskSize; n++)
_weightMask[n] = _weights[n];
}
else if (weighting == "A") {
for (int n=0; n<_maskSize; n++)
_weightMask[n] = _aWeighting[n];
}
else if (weighting == "B") {
for (int n=0; n<_maskSize; n++)
_weightMask[n] = _bWeighting[n];
}
else if (weighting == "C") {
for (int n=0; n<_maskSize; n++)
_weightMask[n] = _cWeighting[n];
}
else if (weighting == "D") {
for (int n=0; n<_maskSize; n++)
_weightMask[n] = _dWeighting[n];
}

for (i=0; i < int(_weight.size()); ++i) {
freq = (Real)i/(Real)_frameSize*_sampleRate;
while (freq > _freqsMask[j]) {
Expand Down Expand Up @@ -189,8 +245,9 @@ void PitchYinFFT::compute() {
yinMin = -_amplitudes[0];
}
else {
// TODO this should never happen, but some people reported it happening in their real time applications.
throw EssentiaException("PitchYinFFT: it appears that no peaks were found by PeakDetection. If you read this message, PLEASE, report this issue to the developers with an example of audio on which it happened.");
tau = 0.0; // it will provide zero-pitch and zero-pitch confidence.
// launch warning message for user feedbacking
E_WARNING("PitchYinFFT: it appears that no peaks were found by PeakDetection algorithm. So, pitch and confidence will be set to zero.");
}
}
else {
Expand Down
4 changes: 3 additions & 1 deletion src/algorithms/tonal/pitchyinfft.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class PitchYinFFT : public Algorithm {
int _tauMin;
int _tauMax;
Real _tolerance;
std::string _weighting;

public:
PitchYinFFT() {
Expand All @@ -83,12 +84,13 @@ class PitchYinFFT : public Algorithm {
declareParameter("maxFrequency", "the maximum allowed frequency [Hz]", "(0,inf)", 22050.0);
declareParameter("interpolate", "boolean flag to enable interpolation", "{true,false}", true);
declareParameter("tolerance", "tolerance for peak detection", "[0,1]", 1.0);
declareParameter("weighting", "string to assign a weighting function", "{default,A,B,C,D,Z}", "default");
}

void configure();
void compute();

void spectralWeights();
void spectralWeights(std::string weighting);

static const char* name;
static const char* category;
Expand Down
5 changes: 3 additions & 2 deletions test/src/unittests/all_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
# Add sys path to make python recognize tests/src/unittests as a module
parent_dir = os.path.abspath(os.path.dirname(tests_dir))
sys.path.insert(0, parent_dir)

# Chdir into the tests dir so that the paths work out right
os.chdir(tests_dir)

Expand Down Expand Up @@ -109,7 +109,8 @@ def computeResetCompute(algo, *args, **kwargs):
'BandReject', 'EqualLoudness', 'MovingAverage' ]
special = [ 'FrameCutter', 'OverlapAdd', 'TempoScaleBands', 'TempoTap', 'TempoTapTicks',
'Panning','OnsetDetection', 'MonoWriter', 'Flux', 'StartStopSilence',
'LogSpectrum', 'ClickDetector', 'SNR', 'SaturationDetector', 'Welch' ]
'LogSpectrum', 'ClickDetector', 'SNR', 'SaturationDetector', 'Welch',
'FrameBuffer']

if algo.name() in audioLoaders + filters + special:
return algo.normalCompute(*args, **kwargs)
Expand Down
68 changes: 68 additions & 0 deletions test/src/unittests/standard/test_framebuffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python

# Copyright (C) 2006-2021 Music Technology Group - Universitat Pompeu Fabra
#
# This file is part of Essentia
#
# Essentia is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the Affero GNU General Public License
# version 3 along with this program. If not, see http://www.gnu.org/licenses/

from essentia_test import *


class TestFrameBuffer(TestCase):

def testEmpty(self):
with self.assertRaises(RuntimeError):
FrameBuffer()([])

def testBufferZeroPadding(self):
buffer = FrameBuffer(bufferSize=8, zeroPadding=True)
self.assertEqualVector(buffer([1, 2]), [0., 0., 0., 0., 0., 0., 1., 2.])
self.assertEqualVector(buffer([3, 4]), [0., 0., 0., 0., 1., 2., 3., 4.])
self.assertEqualVector(buffer([5, 6]), [0., 0., 1., 2., 3., 4., 5., 6.])
self.assertEqualVector(buffer([7, 8]), [1., 2., 3., 4., 5., 6., 7., 8.])
self.assertEqualVector(buffer([9, 10]), [3., 4., 5., 6., 7., 8., 9., 10.])

def testBufferNoZeroPadding(self):
buffer = FrameBuffer(bufferSize=8, zeroPadding=False)
self.assertEqualVector(buffer([1, 2]), [])
self.assertEqualVector(buffer([3, 4]), [])
self.assertEqualVector(buffer([5, 6]), [])
self.assertEqualVector(buffer([7, 8]), [1., 2., 3., 4., 5., 6., 7., 8.])

def testFrameSizeEqualsBufferSize(self):
buffer = FrameBuffer(bufferSize=8)
self.assertEqualVector(buffer([1, 2, 3, 4, 5, 6, 7, 8]), [1., 2., 3., 4., 5., 6., 7., 8.])

def testFrameSizeLargerBufferSize(self):
buffer = FrameBuffer(bufferSize=8)
self.assertEqualVector(buffer([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), [3., 4., 5., 6., 7., 8., 9., 10.])

def testResetZeroPadding(self):
buffer = FrameBuffer(bufferSize=8, zeroPadding=True)
buffer([1, 2, 3, 4, 5, 6]) # Results in [0., 0., 1., 2., 3., 4., 5., 6.]
buffer.reset() # Sets the buffer to zero vector.
self.assertEqualVector(buffer([1, 2]), [0., 0., 0., 0., 0., 0., 1., 2.])

def testResetNoZeroPadding(self):
buffer = FrameBuffer(bufferSize=8, zeroPadding=False)
buffer([1, 2, 3, 4, 5, 6, 7, 8])
buffer.reset()
self.assertEqualVector(buffer([1, 2]), [])


suite = allTests(TestFrameBuffer)

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