Hi @dave96,
Thanks a bunch for taking the time to explain.
So the melFrequencySpectrum
function is below:
template <class T>
std::vector<T> MFCC<T>::melFrequencySpectrum (std::vector<T> magnitudeSpectrum)
{
for (int i = 0; i < numCoefficents; i++)
{
double coeff = 0;
for (int j = 0; j < magnitudeSpectrum.size(); j++)
{
coeff += (T)((magnitudeSpectrum[j] * magnitudeSpectrum[j]) * filterBank[i][j]);
}
melSpectrum[i] = coeff;
}
return melSpectrum;
}
The only reason is suggested std::move()
was due to melSpec being a member variable. In the current version of the library melSpec
is declared as a local/temporary in the melFrequencyCepstralCoefficients
function as below:
template <class T>
std::vector<T> MFCC<T>::melFrequencyCepstralCoefficients (std::vector<T> magnitudeSpectrum)
{
std::vector<T> melSpec;
melSpec = melFrequencySpectrum (magnitudeSpectrum);
for (int i = 0; i < melSpec.size(); i++)
{
melSpec[i] = log (melSpec[i] + (T)FLT_MIN);
}
return discreteCosineTransform (melSpec);
}
As I understand it return value optimisations will work only for local temporaries and rvalues ? So for a member variable you would need to either return a reference/const reference to the member vector or call std::move(returnVector)
, assuming that it is not an issue for the member variables contents to be moved out.
These methods would be called once in every processBlock()
call for example so the vectors returned by either const reference or via std::move()
would be re-populated / modified every time these methods are called which should mean the move would OK ?
At the moment I’ve just changed things so that const vector references are returned from all functions like those above.
So things have been altered to something like the below:
//==================================================================
template <class T>
const std::vector<T>& MFCC<T>::melFrequencyCepstralCoefficients (const std::vector<T>& magnitudeSpectrum)
{
melSpecCoefficients = melFrequencySpectrum (magnitudeSpectrum);
for (int i = 0; i < melSpecCoefficients.size(); i++)
{
melSpecCoefficients[i] = log (melSpecCoefficients[i] + (T)FLT_MIN);
}
return discreteCosineTransform (melSpecCoefficients);
}
//==================================================================
template <class T>
const std::vector<T>& MFCC<T>::melFrequencySpectrum (const std::vector<T>& magnitudeSpectrum)
{
for (int i = 0; i < numCoefficents; i++)
{
double coeff = 0;
for (int j = 0; j < magnitudeSpectrum.size(); j++)
{
coeff += (T)((magnitudeSpectrum[j] * magnitudeSpectrum[j]) * filterBank[i][j]);
}
melSpectrum[i] = coeff;
}
return melSpectrum;
}
//==================================================================
template <class T>
const std::vector<T>& MFCC<T>::discreteCosineTransform (const std::vector<T>& inputSignal)
{
T N = (T)inputSignal.size();
T piOverN = M_PI / N;
for (int k = 0; k < dctOutputSignal.size(); k++)
{
T sum = 0;
T kVal = (T)k;
for (int n = 0; n < inputSignal.size(); n++)
{
T tmp = piOverN * (((T)n) + 0.5) * kVal;
sum += inputSignal[n] * cos (tmp);
}
dctOutputSignal[k] = (T)(2 * sum);
}
return dctOutputSignal;
}
Edit: in discreteCosineTransform
the variable dctOutputSignal
is also a pre-allocated member vector.
Obviously the above is probably not ideal (pretty scrappy) but would you say this approach can be safely called from the callback thread for now ? The const vector references passed as parameters are never modified or resized so everything can be passed as const. It’s possible to do this for all the methods in the library as far as I can tell so I have taken this approach throughout in my own fork. The magnitudeSpectrum
vector for instance is a member variable of the MFCC<T>
objects owning class, so I think I’m right in saying the only way to avoid copying is to pass it as a const reference as above ?
Basically there are a few functions in the library where vectors like melSpec
are created as local temporaries inside the function scope and either have `std::vector.resize()``` called or are assigned to as per the function above. I think I’m right in saying this is all no-go behaviour for real-time audio…
As a quick and dirty solution to get things working for my plugin I basically changed vectors like melSpec
to be member variables which are now sized/allocated and initialised in their owning class’s constructor (not inside any real-tme calls).
I would go with the melFrequencyCepstralCoefficients(const T* buffer, size_t bufferSize)
approach also but for now I’m just trying to make sure I’ve got my head around move semantics and return value optimisations correctly. Keeping the vectors and changing to some const references was a much easier/quicker change.
I’d just like to make sure that the approach I have taken above is actually real-time safe. The author of the lib is looking into all these but is hesitant to remove the use of std::vector
for the moment. However he does have a plugin using the library so I think he’ll be up for the necessary changes.
I’ll probably consider rolling out my own feature extraction routines after the prototype stage if the library has not been updated in time but for now I’d like to keep using my modified fork provided I’ve got my head around things correctly and I’m not causing any blocking behaviour.
Sorry I realise this was a pretty large post/reply. Really appreciate the lessons!
Cheers