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

Framebuffer #1377

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
5 changes: 3 additions & 2 deletions src/algorithms/tonal/pitchyinfft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,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
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)