Reorganise into faster (R2) and finer (R3)
This commit is contained in:
72
src/faster/AudioCurveCalculator.cpp
Normal file
72
src/faster/AudioCurveCalculator.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "AudioCurveCalculator.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
static const int MaxPerceivedFreq = 16000;
|
||||
|
||||
AudioCurveCalculator::AudioCurveCalculator(Parameters parameters) :
|
||||
m_sampleRate(parameters.sampleRate),
|
||||
m_fftSize(parameters.fftSize)
|
||||
{
|
||||
recalculateLastPerceivedBin();
|
||||
}
|
||||
|
||||
AudioCurveCalculator::~AudioCurveCalculator()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AudioCurveCalculator::setSampleRate(int newRate)
|
||||
{
|
||||
m_sampleRate = newRate;
|
||||
recalculateLastPerceivedBin();
|
||||
}
|
||||
|
||||
void
|
||||
AudioCurveCalculator::setFftSize(int newSize)
|
||||
{
|
||||
m_fftSize = newSize;
|
||||
recalculateLastPerceivedBin();
|
||||
}
|
||||
|
||||
void
|
||||
AudioCurveCalculator::recalculateLastPerceivedBin()
|
||||
{
|
||||
if (m_sampleRate == 0) {
|
||||
m_lastPerceivedBin = 0;
|
||||
return;
|
||||
}
|
||||
m_lastPerceivedBin = ((MaxPerceivedFreq * m_fftSize) / m_sampleRate);
|
||||
if (m_lastPerceivedBin > m_fftSize/2) {
|
||||
m_lastPerceivedBin = m_fftSize/2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
134
src/faster/AudioCurveCalculator.h
Normal file
134
src/faster/AudioCurveCalculator.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_AUDIO_CURVE_CALCULATOR_H
|
||||
#define RUBBERBAND_AUDIO_CURVE_CALCULATOR_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../common/sysutils.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
/**
|
||||
* AudioCurveCalculator turns a sequence of audio "columns" --
|
||||
* short-time spectrum magnitude blocks -- into a sequence of numbers
|
||||
* representing some quality of the input such as power or likelihood
|
||||
* of an onset occurring.
|
||||
*
|
||||
* These are typically low-level building-blocks: AudioCurveCalculator
|
||||
* is a simple causal interface in which each input column corresponds
|
||||
* to exactly one output value which is returned immediately. They
|
||||
* have far less power (because of the causal interface and
|
||||
* magnitude-only input) and flexibility (because of the limited
|
||||
* return types) than for example the Vamp plugin interface.
|
||||
*
|
||||
* AudioCurveCalculator implementations typically remember the history
|
||||
* of their processing data, and the caller must call reset() before
|
||||
* resynchronising to an unrelated piece of input audio.
|
||||
*/
|
||||
class AudioCurveCalculator
|
||||
{
|
||||
public:
|
||||
struct Parameters {
|
||||
Parameters(int _sampleRate, int _fftSize) :
|
||||
sampleRate(_sampleRate),
|
||||
fftSize(_fftSize)
|
||||
{ }
|
||||
int sampleRate;
|
||||
int fftSize;
|
||||
};
|
||||
|
||||
AudioCurveCalculator(Parameters parameters);
|
||||
virtual ~AudioCurveCalculator();
|
||||
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getFftSize() const { return m_fftSize; }
|
||||
|
||||
virtual void setSampleRate(int newRate);
|
||||
virtual void setFftSize(int newSize);
|
||||
|
||||
Parameters getParameters() const {
|
||||
return Parameters(m_sampleRate, m_fftSize);
|
||||
}
|
||||
void setParameters(Parameters p) {
|
||||
setSampleRate(p.sampleRate);
|
||||
setFftSize(p.fftSize);
|
||||
}
|
||||
|
||||
// You may not mix calls to the various process functions on a
|
||||
// given instance
|
||||
|
||||
|
||||
/**
|
||||
* Process the given magnitude spectrum block and return the curve
|
||||
* value for it. The mag input contains (fftSize/2 + 1) values
|
||||
* corresponding to the magnitudes of the complex FFT output bins
|
||||
* for a windowed input of size fftSize. The hop (expressed in
|
||||
* time-domain audio samples) from the previous to the current
|
||||
* input block is given by increment.
|
||||
*/
|
||||
virtual float processFloat(const float *R__ mag, int increment) = 0;
|
||||
|
||||
/**
|
||||
* Process the given magnitude spectrum block and return the curve
|
||||
* value for it. The mag input contains (fftSize/2 + 1) values
|
||||
* corresponding to the magnitudes of the complex FFT output bins
|
||||
* for a windowed input of size fftSize. The hop (expressed in
|
||||
* time-domain audio samples) from the previous to the current
|
||||
* input block is given by increment.
|
||||
*/
|
||||
virtual double processDouble(const double *R__ mag, int increment) = 0;
|
||||
|
||||
/**
|
||||
* Obtain a confidence for the curve value (if applicable). A
|
||||
* value of 1.0 indicates perfect confidence in the curve
|
||||
* calculation, 0.0 indicates none.
|
||||
*/
|
||||
virtual double getConfidence() const { return 1.0; }
|
||||
|
||||
/**
|
||||
* Reset the calculator, forgetting the history of the audio input
|
||||
* so far.
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
/**
|
||||
* If the output of this calculator has a known unit, return it as
|
||||
* text. For example, "Hz" or "V".
|
||||
*/
|
||||
virtual const char *getUnit() const { return ""; }
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
int m_fftSize;
|
||||
int m_lastPerceivedBin;
|
||||
void recalculateLastPerceivedBin();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
167
src/faster/CompoundAudioCurve.cpp
Normal file
167
src/faster/CompoundAudioCurve.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "CompoundAudioCurve.h"
|
||||
|
||||
#include "../common/MovingMedian.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
|
||||
CompoundAudioCurve::CompoundAudioCurve(Parameters parameters) :
|
||||
AudioCurveCalculator(parameters),
|
||||
m_percussive(parameters),
|
||||
m_hf(parameters),
|
||||
m_hfFilter(new MovingMedian<double>(19, 85)),
|
||||
m_hfDerivFilter(new MovingMedian<double>(19, 90)),
|
||||
m_type(CompoundDetector),
|
||||
m_lastHf(0.0),
|
||||
m_lastResult(0.0),
|
||||
m_risingCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
CompoundAudioCurve::~CompoundAudioCurve()
|
||||
{
|
||||
delete m_hfFilter;
|
||||
delete m_hfDerivFilter;
|
||||
}
|
||||
|
||||
void
|
||||
CompoundAudioCurve::setType(Type type)
|
||||
{
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
void
|
||||
CompoundAudioCurve::reset()
|
||||
{
|
||||
m_percussive.reset();
|
||||
m_hf.reset();
|
||||
m_hfFilter->reset();
|
||||
m_hfDerivFilter->reset();
|
||||
m_lastHf = 0.0;
|
||||
m_lastResult = 0.0;
|
||||
}
|
||||
|
||||
void
|
||||
CompoundAudioCurve::setFftSize(int newSize)
|
||||
{
|
||||
m_percussive.setFftSize(newSize);
|
||||
m_hf.setFftSize(newSize);
|
||||
m_fftSize = newSize;
|
||||
m_lastHf = 0.0;
|
||||
m_lastResult = 0.0;
|
||||
}
|
||||
|
||||
float
|
||||
CompoundAudioCurve::processFloat(const float *R__ mag, int increment)
|
||||
{
|
||||
float percussive = 0.f;
|
||||
float hf = 0.f;
|
||||
switch (m_type) {
|
||||
case PercussiveDetector:
|
||||
percussive = m_percussive.processFloat(mag, increment);
|
||||
break;
|
||||
case CompoundDetector:
|
||||
percussive = m_percussive.processFloat(mag, increment);
|
||||
hf = m_hf.processFloat(mag, increment);
|
||||
break;
|
||||
case SoftDetector:
|
||||
hf = m_hf.processFloat(mag, increment);
|
||||
break;
|
||||
}
|
||||
return processFiltering(percussive, hf);
|
||||
}
|
||||
|
||||
double
|
||||
CompoundAudioCurve::processDouble(const double *R__ mag, int increment)
|
||||
{
|
||||
double percussive = 0.0;
|
||||
double hf = 0.0;
|
||||
switch (m_type) {
|
||||
case PercussiveDetector:
|
||||
percussive = m_percussive.processDouble(mag, increment);
|
||||
break;
|
||||
case CompoundDetector:
|
||||
percussive = m_percussive.processDouble(mag, increment);
|
||||
hf = m_hf.processDouble(mag, increment);
|
||||
break;
|
||||
case SoftDetector:
|
||||
hf = m_hf.processDouble(mag, increment);
|
||||
break;
|
||||
}
|
||||
return processFiltering(percussive, hf);
|
||||
}
|
||||
|
||||
double
|
||||
CompoundAudioCurve::processFiltering(double percussive, double hf)
|
||||
{
|
||||
if (m_type == PercussiveDetector) {
|
||||
return percussive;
|
||||
}
|
||||
|
||||
double rv = 0.f;
|
||||
|
||||
double hfDeriv = hf - m_lastHf;
|
||||
|
||||
m_hfFilter->push(hf);
|
||||
m_hfDerivFilter->push(hfDeriv);
|
||||
|
||||
double hfFiltered = m_hfFilter->get();
|
||||
double hfDerivFiltered = m_hfDerivFilter->get();
|
||||
|
||||
m_lastHf = hf;
|
||||
|
||||
double result = 0.f;
|
||||
|
||||
double hfExcess = hf - hfFiltered;
|
||||
|
||||
if (hfExcess > 0.0) {
|
||||
result = hfDeriv - hfDerivFiltered;
|
||||
}
|
||||
|
||||
if (result < m_lastResult) {
|
||||
if (m_risingCount > 3 && m_lastResult > 0) rv = 0.5;
|
||||
m_risingCount = 0;
|
||||
} else {
|
||||
m_risingCount ++;
|
||||
}
|
||||
|
||||
if (m_type == CompoundDetector) {
|
||||
if (percussive > 0.35 && percussive > rv) {
|
||||
rv = percussive;
|
||||
}
|
||||
}
|
||||
|
||||
m_lastResult = result;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
73
src/faster/CompoundAudioCurve.h
Normal file
73
src/faster/CompoundAudioCurve.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_COMPOUND_AUDIO_CURVE_H
|
||||
#define RUBBERBAND_COMPOUND_AUDIO_CURVE_H
|
||||
|
||||
#include "PercussiveAudioCurve.h"
|
||||
#include "HighFrequencyAudioCurve.h"
|
||||
#include "../common/SampleFilter.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class CompoundAudioCurve : public AudioCurveCalculator
|
||||
{
|
||||
public:
|
||||
CompoundAudioCurve(Parameters parameters);
|
||||
|
||||
virtual ~CompoundAudioCurve();
|
||||
|
||||
enum Type {
|
||||
PercussiveDetector,
|
||||
CompoundDetector,
|
||||
SoftDetector
|
||||
};
|
||||
virtual void setType(Type); // default is CompoundDetector
|
||||
|
||||
virtual void setFftSize(int newSize);
|
||||
|
||||
virtual float processFloat(const float *R__ mag, int increment);
|
||||
virtual double processDouble(const double *R__ mag, int increment);
|
||||
|
||||
virtual void reset();
|
||||
|
||||
protected:
|
||||
PercussiveAudioCurve m_percussive;
|
||||
HighFrequencyAudioCurve m_hf;
|
||||
|
||||
SampleFilter<double> *m_hfFilter;
|
||||
SampleFilter<double> *m_hfDerivFilter;
|
||||
|
||||
Type m_type;
|
||||
|
||||
double m_lastHf;
|
||||
double m_lastResult;
|
||||
int m_risingCount;
|
||||
|
||||
double processFiltering(double percussive, double hf);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
73
src/faster/HighFrequencyAudioCurve.cpp
Normal file
73
src/faster/HighFrequencyAudioCurve.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "HighFrequencyAudioCurve.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
|
||||
HighFrequencyAudioCurve::HighFrequencyAudioCurve(Parameters parameters) :
|
||||
AudioCurveCalculator(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
HighFrequencyAudioCurve::~HighFrequencyAudioCurve()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
HighFrequencyAudioCurve::reset()
|
||||
{
|
||||
}
|
||||
|
||||
float
|
||||
HighFrequencyAudioCurve::processFloat(const float *R__ mag, int)
|
||||
{
|
||||
float result = 0.0;
|
||||
|
||||
const int sz = m_lastPerceivedBin;
|
||||
|
||||
for (int n = 0; n <= sz; ++n) {
|
||||
result = result + mag[n] * n;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double
|
||||
HighFrequencyAudioCurve::processDouble(const double *R__ mag, int)
|
||||
{
|
||||
double result = 0.0;
|
||||
|
||||
const int sz = m_lastPerceivedBin;
|
||||
|
||||
for (int n = 0; n <= sz; ++n) {
|
||||
result = result + mag[n] * n;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
47
src/faster/HighFrequencyAudioCurve.h
Normal file
47
src/faster/HighFrequencyAudioCurve.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H
|
||||
#define RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H
|
||||
|
||||
#include "AudioCurveCalculator.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class HighFrequencyAudioCurve : public AudioCurveCalculator
|
||||
{
|
||||
public:
|
||||
HighFrequencyAudioCurve(Parameters parameters);
|
||||
|
||||
virtual ~HighFrequencyAudioCurve();
|
||||
|
||||
virtual float processFloat(const float *R__ mag, int increment);
|
||||
virtual double processDouble(const double *R__ mag, int increment);
|
||||
virtual void reset();
|
||||
virtual const char *getUnit() const { return "Vbin"; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
114
src/faster/PercussiveAudioCurve.cpp
Normal file
114
src/faster/PercussiveAudioCurve.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "PercussiveAudioCurve.h"
|
||||
|
||||
#include "../common/Allocators.h"
|
||||
#include "../common/VectorOps.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
|
||||
PercussiveAudioCurve::PercussiveAudioCurve(Parameters parameters) :
|
||||
AudioCurveCalculator(parameters)
|
||||
{
|
||||
m_prevMag = allocate_and_zero<double>(m_fftSize/2 + 1);
|
||||
}
|
||||
|
||||
PercussiveAudioCurve::~PercussiveAudioCurve()
|
||||
{
|
||||
deallocate(m_prevMag);
|
||||
}
|
||||
|
||||
void
|
||||
PercussiveAudioCurve::reset()
|
||||
{
|
||||
v_zero(m_prevMag, m_fftSize/2 + 1);
|
||||
}
|
||||
|
||||
void
|
||||
PercussiveAudioCurve::setFftSize(int newSize)
|
||||
{
|
||||
m_prevMag = reallocate(m_prevMag, m_fftSize/2 + 1, newSize/2 + 1);
|
||||
AudioCurveCalculator::setFftSize(newSize);
|
||||
reset();
|
||||
}
|
||||
|
||||
float
|
||||
PercussiveAudioCurve::processFloat(const float *R__ mag, int)
|
||||
{
|
||||
static float threshold = powf(10.f, 0.15f); // 3dB rise in square of magnitude
|
||||
static float zeroThresh = powf(10.f, -8);
|
||||
|
||||
int count = 0;
|
||||
int nonZeroCount = 0;
|
||||
|
||||
const int sz = m_lastPerceivedBin;
|
||||
|
||||
for (int n = 1; n <= sz; ++n) {
|
||||
float v = 0.f;
|
||||
if (m_prevMag[n] > zeroThresh) v = mag[n] / m_prevMag[n];
|
||||
else if (mag[n] > zeroThresh) v = threshold;
|
||||
bool above = (v >= threshold);
|
||||
if (above) ++count;
|
||||
if (mag[n] > zeroThresh) ++nonZeroCount;
|
||||
}
|
||||
|
||||
v_convert(m_prevMag, mag, sz + 1);
|
||||
|
||||
if (nonZeroCount == 0) return 0;
|
||||
else return float(count) / float(nonZeroCount);
|
||||
}
|
||||
|
||||
double
|
||||
PercussiveAudioCurve::processDouble(const double *R__ mag, int)
|
||||
{
|
||||
static double threshold = pow(10., 0.15); // 3dB rise in square of magnitude
|
||||
static double zeroThresh = pow(10., -8);
|
||||
|
||||
int count = 0;
|
||||
int nonZeroCount = 0;
|
||||
|
||||
const int sz = m_lastPerceivedBin;
|
||||
|
||||
for (int n = 1; n <= sz; ++n) {
|
||||
double v = 0.0;
|
||||
if (m_prevMag[n] > zeroThresh) v = mag[n] / m_prevMag[n];
|
||||
else if (mag[n] > zeroThresh) v = threshold;
|
||||
bool above = (v >= threshold);
|
||||
if (above) ++count;
|
||||
if (mag[n] > zeroThresh) ++nonZeroCount;
|
||||
}
|
||||
|
||||
v_copy(m_prevMag, mag, sz + 1);
|
||||
|
||||
if (nonZeroCount == 0) return 0;
|
||||
else return double(count) / double(nonZeroCount);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
54
src/faster/PercussiveAudioCurve.h
Normal file
54
src/faster/PercussiveAudioCurve.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H
|
||||
#define RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H
|
||||
|
||||
#include "AudioCurveCalculator.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class PercussiveAudioCurve : public AudioCurveCalculator
|
||||
{
|
||||
public:
|
||||
PercussiveAudioCurve(Parameters parameters);
|
||||
|
||||
virtual ~PercussiveAudioCurve();
|
||||
|
||||
virtual void setFftSize(int newSize);
|
||||
|
||||
virtual float processFloat(const float *R__ mag, int increment);
|
||||
virtual double processDouble(const double *R__ mag, int increment);
|
||||
|
||||
|
||||
virtual void reset();
|
||||
virtual const char *getUnit() const { return "bin/total"; }
|
||||
|
||||
protected:
|
||||
double *R__ m_prevMag;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
73
src/faster/SilentAudioCurve.cpp
Normal file
73
src/faster/SilentAudioCurve.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "SilentAudioCurve.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
|
||||
SilentAudioCurve::SilentAudioCurve(Parameters parameters) :
|
||||
AudioCurveCalculator(parameters)
|
||||
{
|
||||
}
|
||||
|
||||
SilentAudioCurve::~SilentAudioCurve()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SilentAudioCurve::reset()
|
||||
{
|
||||
}
|
||||
|
||||
float
|
||||
SilentAudioCurve::processFloat(const float *R__ mag, int)
|
||||
{
|
||||
const int hs = m_lastPerceivedBin;
|
||||
static float threshold = powf(10.f, -6);
|
||||
|
||||
for (int i = 0; i <= hs; ++i) {
|
||||
if (mag[i] > threshold) return 0.f;
|
||||
}
|
||||
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
double
|
||||
SilentAudioCurve::processDouble(const double *R__ mag, int)
|
||||
{
|
||||
const int hs = m_lastPerceivedBin;
|
||||
static double threshold = pow(10.0, -6);
|
||||
|
||||
for (int i = 0; i <= hs; ++i) {
|
||||
if (mag[i] > threshold) return 0.f;
|
||||
}
|
||||
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
46
src/faster/SilentAudioCurve.h
Normal file
46
src/faster/SilentAudioCurve.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_SILENT_AUDIO_CURVE_H
|
||||
#define RUBBERBAND_SILENT_AUDIO_CURVE_H
|
||||
|
||||
#include "AudioCurveCalculator.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class SilentAudioCurve : public AudioCurveCalculator
|
||||
{
|
||||
public:
|
||||
SilentAudioCurve(Parameters parameters);
|
||||
virtual ~SilentAudioCurve();
|
||||
|
||||
virtual float processFloat(const float *R__ mag, int increment);
|
||||
virtual double processDouble(const double *R__ mag, int increment);
|
||||
virtual void reset();
|
||||
virtual const char *getUnit() const { return "bool"; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
155
src/faster/SincWindow.h
Normal file
155
src/faster/SincWindow.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_SINC_WINDOW_H
|
||||
#define RUBBERBAND_SINC_WINDOW_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
|
||||
#include "../common/sysutils.h"
|
||||
#include "../common/VectorOps.h"
|
||||
#include "../common/Allocators.h"
|
||||
|
||||
namespace RubberBand {
|
||||
|
||||
template <typename T>
|
||||
class SincWindow
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a sinc windower which produces a window of size n
|
||||
* containing the values of sinc(x) with x=0 at index n/2, such
|
||||
* that the distance from -pi to pi (the point at which the sinc
|
||||
* function first crosses zero, for negative and positive
|
||||
* arguments respectively) is p samples.
|
||||
*/
|
||||
SincWindow(int n, int p) : m_size(n), m_p(p), m_cache(0) {
|
||||
encache();
|
||||
}
|
||||
SincWindow(const SincWindow &w) : m_size(w.m_size), m_p(w.m_p), m_cache(0) {
|
||||
encache();
|
||||
}
|
||||
SincWindow &operator=(const SincWindow &w) {
|
||||
if (&w == this) return *this;
|
||||
m_size = w.m_size;
|
||||
m_p = w.m_p;
|
||||
m_cache = 0;
|
||||
encache();
|
||||
return *this;
|
||||
}
|
||||
virtual ~SincWindow() {
|
||||
deallocate(m_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the sinc window with the same size, but a new scale
|
||||
* (the p value is interpreted as for the argument of the same
|
||||
* name to the constructor). If p is unchanged from the previous
|
||||
* value, do nothing (quickly).
|
||||
*/
|
||||
inline void rewrite(int p) {
|
||||
if (m_p == p) return;
|
||||
m_p = p;
|
||||
encache();
|
||||
}
|
||||
|
||||
inline void cut(T *const R__ dst) const {
|
||||
v_multiply(dst, m_cache, m_size);
|
||||
}
|
||||
|
||||
inline void cut(const T *const R__ src, T *const R__ dst) const {
|
||||
v_multiply(dst, src, m_cache, m_size);
|
||||
}
|
||||
|
||||
inline void add(T *const R__ dst, T scale) const {
|
||||
v_add_with_gain(dst, m_cache, scale, m_size);
|
||||
}
|
||||
|
||||
inline T getArea() const { return m_area; }
|
||||
inline T getValue(int i) const { return m_cache[i]; }
|
||||
|
||||
inline int getSize() const { return m_size; }
|
||||
inline int getP() const { return m_p; }
|
||||
|
||||
/**
|
||||
* Write a sinc window of size n with scale p (the p value is
|
||||
* interpreted as for the argument of the same name to the
|
||||
* constructor).
|
||||
*/
|
||||
static
|
||||
void write(T *const R__ dst, const int n, const int p) {
|
||||
const int half = n/2;
|
||||
writeHalf(dst, n, p);
|
||||
int target = half - 1;
|
||||
for (int i = half + 1; i < n; ++i) {
|
||||
dst[target--] = dst[i];
|
||||
}
|
||||
const T twopi = T(2. * M_PI);
|
||||
T arg = T(half) * twopi / p;
|
||||
dst[0] = sin(arg) / arg;
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_size;
|
||||
int m_p;
|
||||
T *R__ m_cache;
|
||||
T m_area;
|
||||
|
||||
/**
|
||||
* Write the positive half (i.e. n/2 to n-1) of a sinc window of
|
||||
* size n with scale p (the p value is interpreted as for the
|
||||
* argument of the same name to the constructor). The negative
|
||||
* half (indices 0 to n/2-1) of dst is left unchanged.
|
||||
*/
|
||||
static
|
||||
void writeHalf(T *const R__ dst, const int n, const int p) {
|
||||
const int half = n/2;
|
||||
const T twopi = T(2. * M_PI);
|
||||
dst[half] = T(1.0);
|
||||
for (int i = 1; i < half; ++i) {
|
||||
T arg = T(i) * twopi / p;
|
||||
dst[half+i] = sin(arg) / arg;
|
||||
}
|
||||
}
|
||||
|
||||
void encache() {
|
||||
if (!m_cache) {
|
||||
m_cache = allocate<T>(m_size);
|
||||
}
|
||||
|
||||
write(m_cache, m_size, m_p);
|
||||
|
||||
m_area = 0;
|
||||
for (int i = 0; i < m_size; ++i) {
|
||||
m_area += m_cache[i];
|
||||
}
|
||||
m_area /= m_size;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
865
src/faster/StretchCalculator.cpp
Normal file
865
src/faster/StretchCalculator.cpp
Normal file
@@ -0,0 +1,865 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "StretchCalculator.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../common/sysutils.h"
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
StretchCalculator::StretchCalculator(size_t sampleRate,
|
||||
size_t inputIncrement,
|
||||
bool useHardPeaks) :
|
||||
m_sampleRate(sampleRate),
|
||||
m_increment(inputIncrement),
|
||||
m_prevDf(0),
|
||||
m_prevRatio(1.0),
|
||||
m_prevTimeRatio(1.0),
|
||||
m_transientAmnesty(0),
|
||||
m_debugLevel(0),
|
||||
m_useHardPeaks(useHardPeaks),
|
||||
m_inFrameCounter(0),
|
||||
m_frameCheckpoint(0, 0),
|
||||
m_outFrameCounter(0)
|
||||
{
|
||||
// std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl;
|
||||
}
|
||||
|
||||
StretchCalculator::~StretchCalculator()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
StretchCalculator::setKeyFrameMap(const std::map<size_t, size_t> &mapping)
|
||||
{
|
||||
m_keyFrameMap = mapping;
|
||||
|
||||
// Ensure we always have a 0 -> 0 mapping. If there's nothing in
|
||||
// the map at all, don't need to worry about this (empty map is
|
||||
// handled separately anyway)
|
||||
if (!m_keyFrameMap.empty()) {
|
||||
if (m_keyFrameMap.find(0) == m_keyFrameMap.end()) {
|
||||
m_keyFrameMap[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int>
|
||||
StretchCalculator::calculate(double ratio, size_t inputDuration,
|
||||
const std::vector<float> &phaseResetDf)
|
||||
{
|
||||
m_peaks = findPeaks(phaseResetDf);
|
||||
|
||||
size_t totalCount = phaseResetDf.size();
|
||||
|
||||
size_t outputDuration = lrint(inputDuration * ratio);
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
std::cerr << "StretchCalculator::calculate(): inputDuration " << inputDuration << ", ratio " << ratio << ", outputDuration " << outputDuration;
|
||||
}
|
||||
|
||||
outputDuration = lrint((phaseResetDf.size() * m_increment) * ratio);
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
std::cerr << " (rounded up to " << outputDuration << ")";
|
||||
std::cerr << ", df size " << phaseResetDf.size() << ", increment "
|
||||
<< m_increment << std::endl;
|
||||
}
|
||||
|
||||
std::vector<Peak> peaks; // peak position (in chunks) and hardness
|
||||
std::vector<size_t> targets; // targets for mapping peaks (in samples)
|
||||
mapPeaks(peaks, targets, outputDuration, totalCount);
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "have " << peaks.size() << " fixed positions" << std::endl;
|
||||
}
|
||||
|
||||
size_t totalInput = 0, totalOutput = 0;
|
||||
|
||||
std::vector<int> increments;
|
||||
|
||||
for (size_t i = 0; i <= peaks.size(); ++i) {
|
||||
|
||||
size_t regionStart, regionStartChunk, regionEnd, regionEndChunk;
|
||||
bool phaseReset = false;
|
||||
|
||||
if (i == 0) {
|
||||
regionStartChunk = 0;
|
||||
regionStart = 0;
|
||||
} else {
|
||||
regionStartChunk = peaks[i-1].chunk;
|
||||
regionStart = targets[i-1];
|
||||
phaseReset = peaks[i-1].hard;
|
||||
}
|
||||
|
||||
if (i == peaks.size()) {
|
||||
// std::cerr << "note: i (=" << i << ") == peaks.size(); regionEndChunk " << regionEndChunk << " -> " << totalCount << ", regionEnd " << regionEnd << " -> " << outputDuration << std::endl;
|
||||
regionEndChunk = totalCount;
|
||||
regionEnd = outputDuration;
|
||||
} else {
|
||||
regionEndChunk = peaks[i].chunk;
|
||||
regionEnd = targets[i];
|
||||
}
|
||||
|
||||
if (regionStartChunk > totalCount) regionStartChunk = totalCount;
|
||||
if (regionStart > outputDuration) regionStart = outputDuration;
|
||||
if (regionEndChunk > totalCount) regionEndChunk = totalCount;
|
||||
if (regionEnd > outputDuration) regionEnd = outputDuration;
|
||||
|
||||
if (regionEndChunk < regionStartChunk) regionEndChunk = regionStartChunk;
|
||||
if (regionEnd < regionStart) regionEnd = regionStart;
|
||||
|
||||
size_t regionDuration = regionEnd - regionStart;
|
||||
|
||||
size_t nchunks = regionEndChunk - regionStartChunk;
|
||||
|
||||
if (nchunks == 0) {
|
||||
//!!!
|
||||
break;
|
||||
}
|
||||
|
||||
double per = double(regionDuration) / double(nchunks);
|
||||
double acc = 0.0;
|
||||
size_t nremaining = nchunks;
|
||||
size_t totalForRegion = 0;
|
||||
|
||||
if (phaseReset) {
|
||||
size_t incr;
|
||||
if (nchunks > 1) {
|
||||
incr = m_increment;
|
||||
if (incr > regionDuration) {
|
||||
incr = regionDuration;
|
||||
}
|
||||
} else {
|
||||
incr = regionDuration;
|
||||
}
|
||||
increments.push_back(- int64_t(incr));
|
||||
per = double(regionDuration - incr) / double(nchunks - 1);
|
||||
acc += incr;
|
||||
totalForRegion += incr;
|
||||
totalInput += m_increment;
|
||||
nremaining = nremaining - 1;
|
||||
}
|
||||
|
||||
if (nremaining > 0) {
|
||||
for (size_t j = 0; j+1 < nremaining; ++j) {
|
||||
acc += per;
|
||||
size_t incr = size_t(round(acc - totalForRegion));
|
||||
increments.push_back(incr);
|
||||
totalForRegion += incr;
|
||||
totalInput += m_increment;
|
||||
}
|
||||
if (regionDuration > totalForRegion) {
|
||||
size_t final = regionDuration - totalForRegion;
|
||||
increments.push_back(final);
|
||||
totalForRegion += final;
|
||||
totalInput += m_increment;
|
||||
}
|
||||
}
|
||||
|
||||
totalOutput += totalForRegion;
|
||||
}
|
||||
|
||||
if (m_debugLevel > 0) {
|
||||
std::cerr << "total input increment = " << totalInput << " (= " << totalInput / m_increment << " chunks), output = " << totalOutput << ", ratio = " << double(totalOutput)/double(totalInput) << ", ideal output " << size_t(ceil(totalInput * ratio)) << std::endl;
|
||||
}
|
||||
|
||||
return increments;
|
||||
}
|
||||
|
||||
void
|
||||
StretchCalculator::mapPeaks(std::vector<Peak> &peaks,
|
||||
std::vector<size_t> &targets,
|
||||
size_t outputDuration,
|
||||
size_t totalCount)
|
||||
{
|
||||
// outputDuration is in audio samples; totalCount is in chunks
|
||||
|
||||
if (m_keyFrameMap.empty()) {
|
||||
// "normal" behaviour -- fixed points are strictly in
|
||||
// proportion
|
||||
peaks = m_peaks;
|
||||
for (size_t i = 0; i < peaks.size(); ++i) {
|
||||
targets.push_back
|
||||
(lrint((double(peaks[i].chunk) * outputDuration) / totalCount));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We have been given a set of source -> target sample frames in
|
||||
// m_keyFrameMap. We want to ensure that (to the nearest chunk) these
|
||||
// are followed exactly, and any fixed points that we calculated
|
||||
// ourselves are interpolated in linear proportion in between.
|
||||
|
||||
size_t peakidx = 0;
|
||||
std::map<size_t, size_t>::const_iterator mi = m_keyFrameMap.begin();
|
||||
|
||||
// NB we know for certain we have a mapping from 0 -> 0 (or at
|
||||
// least, some mapping for source sample 0) because that is
|
||||
// enforced in setKeyFrameMap above. However, we aren't guaranteed
|
||||
// to have a mapping for the total duration -- we will usually
|
||||
// need to assume it maps to the normal duration * ratio sample
|
||||
|
||||
while (mi != m_keyFrameMap.end()) {
|
||||
|
||||
// std::cerr << "mi->first is " << mi->first << ", second is " << mi->second <<std::endl;
|
||||
|
||||
// The map we've been given is from sample to sample, but
|
||||
// we can only map from chunk to sample. We should perhaps
|
||||
// adjust the target sample to compensate for the discrepancy
|
||||
// between the chunk position and the exact requested source
|
||||
// sample. But we aren't doing that yet.
|
||||
|
||||
size_t sourceStartChunk = mi->first / m_increment;
|
||||
size_t sourceEndChunk = totalCount;
|
||||
|
||||
size_t targetStartSample = mi->second;
|
||||
size_t targetEndSample = outputDuration;
|
||||
|
||||
++mi;
|
||||
if (mi != m_keyFrameMap.end()) {
|
||||
sourceEndChunk = mi->first / m_increment;
|
||||
targetEndSample = mi->second;
|
||||
}
|
||||
|
||||
if (sourceStartChunk >= totalCount ||
|
||||
sourceStartChunk >= sourceEndChunk ||
|
||||
targetStartSample >= outputDuration ||
|
||||
targetStartSample >= targetEndSample) {
|
||||
std::cerr << "NOTE: ignoring mapping from chunk " << sourceStartChunk << " to sample " << targetStartSample << "\n(source or target chunk exceeds total count, or end is not later than start)" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// one peak and target for the mapping, then one for each of
|
||||
// the computed peaks that appear before the following mapping
|
||||
|
||||
Peak p;
|
||||
p.chunk = sourceStartChunk;
|
||||
p.hard = false; // mappings are in time only, not phase reset points
|
||||
peaks.push_back(p);
|
||||
targets.push_back(targetStartSample);
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "mapped chunk " << sourceStartChunk << " (frame " << sourceStartChunk * m_increment << ") -> " << targetStartSample << std::endl;
|
||||
}
|
||||
|
||||
while (peakidx < m_peaks.size()) {
|
||||
|
||||
size_t pchunk = m_peaks[peakidx].chunk;
|
||||
|
||||
if (pchunk < sourceStartChunk) {
|
||||
// shouldn't happen, should have been dealt with
|
||||
// already -- but no harm in ignoring it explicitly
|
||||
++peakidx;
|
||||
continue;
|
||||
}
|
||||
if (pchunk == sourceStartChunk) {
|
||||
// convert that last peak to a hard one, after all
|
||||
peaks[peaks.size()-1].hard = true;
|
||||
++peakidx;
|
||||
continue;
|
||||
}
|
||||
if (pchunk >= sourceEndChunk) {
|
||||
// leave the rest for after the next mapping
|
||||
break;
|
||||
}
|
||||
p.chunk = pchunk;
|
||||
p.hard = m_peaks[peakidx].hard;
|
||||
|
||||
double proportion =
|
||||
double(pchunk - sourceStartChunk) /
|
||||
double(sourceEndChunk - sourceStartChunk);
|
||||
|
||||
size_t target =
|
||||
targetStartSample +
|
||||
lrint(proportion *
|
||||
(targetEndSample - targetStartSample));
|
||||
|
||||
if (target <= targets[targets.size()-1] + m_increment) {
|
||||
// peaks will become too close together afterwards, ignore
|
||||
++peakidx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << " peak chunk " << pchunk << " (frame " << pchunk * m_increment << ") -> " << target << std::endl;
|
||||
}
|
||||
|
||||
peaks.push_back(p);
|
||||
targets.push_back(target);
|
||||
++peakidx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t
|
||||
StretchCalculator::expectedOutFrame(int64_t inFrame, double timeRatio)
|
||||
{
|
||||
int64_t checkpointedAt = m_frameCheckpoint.first;
|
||||
int64_t checkpointed = m_frameCheckpoint.second;
|
||||
return int64_t(round(checkpointed + (inFrame - checkpointedAt) * timeRatio));
|
||||
}
|
||||
|
||||
int
|
||||
StretchCalculator::calculateSingle(double timeRatio,
|
||||
double effectivePitchRatio,
|
||||
float df,
|
||||
size_t inIncrement,
|
||||
size_t analysisWindowSize,
|
||||
size_t synthesisWindowSize)
|
||||
{
|
||||
double ratio = timeRatio / effectivePitchRatio;
|
||||
|
||||
int increment = int(inIncrement);
|
||||
if (increment == 0) increment = m_increment;
|
||||
|
||||
int outIncrement = lrint(increment * ratio); // the normal case
|
||||
bool isTransient = false;
|
||||
|
||||
// We want to ensure, as close as possible, that the phase reset
|
||||
// points appear at the right audio frame numbers. To this end we
|
||||
// track the incoming frame number, its corresponding expected
|
||||
// output frame number, and the actual output frame number
|
||||
// projected based on the ratios provided.
|
||||
//
|
||||
// There are two subtleties:
|
||||
//
|
||||
// (1) on a ratio change, we need to checkpoint the expected
|
||||
// output frame number reached so far and start counting again
|
||||
// with the new ratio. We could do this with a reset to zero, but
|
||||
// it's easier to reason about absolute input/output frame
|
||||
// matches, so for the moment at least we're doing this by
|
||||
// explicitly checkpointing the current numbers (hence the use of
|
||||
// the above expectedOutFrame() function which refers to the
|
||||
// last checkpointed values).
|
||||
//
|
||||
// (2) in the case of a pitch shift in a configuration where
|
||||
// resampling occurs after stretching, all of our output
|
||||
// increments will be effectively modified by resampling after we
|
||||
// return. This is why we separate out timeRatio and
|
||||
// effectivePitchRatio arguments - the former is the ratio that
|
||||
// has already been applied and the latter is the ratio that will
|
||||
// be applied by any subsequent resampling step (which will be 1.0
|
||||
// / pitchScale if resampling is happening after stretching). So
|
||||
// the overall ratio is timeRatio / effectivePitchRatio.
|
||||
|
||||
bool ratioChanged = (ratio != m_prevRatio);
|
||||
if (ratioChanged) {
|
||||
// Reset our frame counters from the ratio change.
|
||||
|
||||
// m_outFrameCounter tracks the frames counted at output from
|
||||
// this function, which normally precedes resampling - hence
|
||||
// the use of timeRatio rather than ratio here
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "StretchCalculator: ratio changed from " << m_prevRatio << " to " << ratio << std::endl;
|
||||
}
|
||||
|
||||
int64_t toCheckpoint = expectedOutFrame
|
||||
(m_inFrameCounter, m_prevTimeRatio);
|
||||
m_frameCheckpoint =
|
||||
std::pair<int64_t, int64_t>(m_inFrameCounter, toCheckpoint);
|
||||
}
|
||||
|
||||
m_prevRatio = ratio;
|
||||
m_prevTimeRatio = timeRatio;
|
||||
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "StretchCalculator::calculateSingle: timeRatio = "
|
||||
<< timeRatio << ", effectivePitchRatio = "
|
||||
<< effectivePitchRatio << " (that's 1.0 / "
|
||||
<< (1.0 / effectivePitchRatio)
|
||||
<< "), ratio = " << ratio << ", df = " << df
|
||||
<< ", inIncrement = " << inIncrement
|
||||
<< ", default outIncrement = " << outIncrement
|
||||
<< ", analysisWindowSize = " << analysisWindowSize
|
||||
<< ", synthesisWindowSize = " << synthesisWindowSize
|
||||
<< std::endl;
|
||||
|
||||
std::cerr << "inFrameCounter = " << m_inFrameCounter
|
||||
<< ", outFrameCounter = " << m_outFrameCounter
|
||||
<< std::endl;
|
||||
|
||||
std::cerr << "The next sample out is input sample " << m_inFrameCounter << std::endl;
|
||||
}
|
||||
|
||||
int64_t intended = expectedOutFrame
|
||||
(m_inFrameCounter + analysisWindowSize/4, timeRatio);
|
||||
int64_t projected = int64_t
|
||||
(round(m_outFrameCounter + (synthesisWindowSize/4 * effectivePitchRatio)));
|
||||
|
||||
int64_t divergence = projected - intended;
|
||||
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << divergence << std::endl;
|
||||
}
|
||||
|
||||
// In principle, the threshold depends on chunk size: larger chunk
|
||||
// sizes need higher thresholds. Since chunk size depends on
|
||||
// ratio, I suppose we could in theory calculate the threshold
|
||||
// from the ratio directly. For the moment we're happy if it
|
||||
// works well in common situations.
|
||||
|
||||
float transientThreshold = 0.35f;
|
||||
// if (ratio > 1) transientThreshold = 0.25f;
|
||||
|
||||
if (m_useHardPeaks && df > m_prevDf * 1.1f && df > transientThreshold) {
|
||||
if (divergence > 1000 || divergence < -1000) {
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "StretchCalculator::calculateSingle: transient, but we're not permitting it because the divergence (" << divergence << ") is too great" << std::endl;
|
||||
}
|
||||
} else {
|
||||
isTransient = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "df = " << df << ", prevDf = " << m_prevDf
|
||||
<< ", thresh = " << transientThreshold << std::endl;
|
||||
}
|
||||
|
||||
m_prevDf = df;
|
||||
|
||||
if (m_transientAmnesty > 0) {
|
||||
if (isTransient) {
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "StretchCalculator::calculateSingle: transient, but we have an amnesty (df " << df << ", threshold " << transientThreshold << ")" << std::endl;
|
||||
}
|
||||
isTransient = false;
|
||||
}
|
||||
--m_transientAmnesty;
|
||||
}
|
||||
|
||||
if (isTransient) {
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "StretchCalculator::calculateSingle: transient at (df " << df << ", threshold " << transientThreshold << ")" << std::endl;
|
||||
}
|
||||
|
||||
// as in offline mode, 0.05 sec approx min between transients
|
||||
m_transientAmnesty =
|
||||
lrint(ceil(double(m_sampleRate) / (20 * double(increment))));
|
||||
|
||||
outIncrement = increment;
|
||||
|
||||
} else {
|
||||
|
||||
double recovery = 0.0;
|
||||
if (divergence > 1000 || divergence < -1000) {
|
||||
recovery = divergence / ((m_sampleRate / 10.0) / increment);
|
||||
} else if (divergence > 100 || divergence < -100) {
|
||||
recovery = divergence / ((m_sampleRate / 20.0) / increment);
|
||||
} else {
|
||||
recovery = divergence / 4.0;
|
||||
}
|
||||
|
||||
int incr = lrint(outIncrement - recovery);
|
||||
if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) {
|
||||
std::cerr << "divergence = " << divergence << ", recovery = " << recovery << ", incr = " << incr << ", ";
|
||||
}
|
||||
|
||||
int minIncr = lrint(increment * ratio * 0.3);
|
||||
int maxIncr = lrint(increment * ratio * 2);
|
||||
|
||||
if (incr < minIncr) {
|
||||
incr = minIncr;
|
||||
} else if (incr > maxIncr) {
|
||||
incr = maxIncr;
|
||||
}
|
||||
|
||||
if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) {
|
||||
std::cerr << "clamped into [" << minIncr << ", " << maxIncr
|
||||
<< "] becomes " << incr << std::endl;
|
||||
}
|
||||
|
||||
if (incr < 0) {
|
||||
std::cerr << "WARNING: internal error: incr < 0 in calculateSingle"
|
||||
<< std::endl;
|
||||
outIncrement = 0;
|
||||
} else {
|
||||
outIncrement = incr;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "StretchCalculator::calculateSingle: returning isTransient = "
|
||||
<< isTransient << ", outIncrement = " << outIncrement
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
m_inFrameCounter += inIncrement;
|
||||
m_outFrameCounter += outIncrement * effectivePitchRatio;
|
||||
|
||||
if (isTransient) {
|
||||
return -outIncrement;
|
||||
} else {
|
||||
return outIncrement;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StretchCalculator::reset()
|
||||
{
|
||||
m_prevDf = 0;
|
||||
m_prevRatio = 1.0;
|
||||
m_prevTimeRatio = 1.0;
|
||||
m_inFrameCounter = 0;
|
||||
m_frameCheckpoint = std::pair<int64_t, int64_t>(0, 0);
|
||||
m_outFrameCounter = 0.0;
|
||||
m_transientAmnesty = 0;
|
||||
m_keyFrameMap.clear();
|
||||
}
|
||||
|
||||
std::vector<StretchCalculator::Peak>
|
||||
StretchCalculator::findPeaks(const std::vector<float> &rawDf)
|
||||
{
|
||||
std::vector<float> df = smoothDF(rawDf);
|
||||
|
||||
// We distinguish between "soft" and "hard" peaks. A soft peak is
|
||||
// simply the result of peak-picking on the smoothed onset
|
||||
// detection function, and it represents any (strong-ish) onset.
|
||||
// We aim to ensure always that soft peaks are placed at the
|
||||
// correct position in time. A hard peak is where there is a very
|
||||
// rapid rise in detection function, and it presumably represents
|
||||
// a more broadband, noisy transient. For these we perform a
|
||||
// phase reset (if in the appropriate mode), and we locate the
|
||||
// reset at the first point where we notice enough of a rapid
|
||||
// rise, rather than necessarily at the peak itself, in order to
|
||||
// preserve the shape of the transient.
|
||||
|
||||
std::set<size_t> hardPeakCandidates;
|
||||
std::set<size_t> softPeakCandidates;
|
||||
|
||||
if (m_useHardPeaks) {
|
||||
|
||||
// 0.05 sec approx min between hard peaks
|
||||
size_t hardPeakAmnesty = lrint(ceil(double(m_sampleRate) /
|
||||
(20 * double(m_increment))));
|
||||
size_t prevHardPeak = 0;
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "hardPeakAmnesty = " << hardPeakAmnesty << std::endl;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i + 1 < df.size(); ++i) {
|
||||
|
||||
if (df[i] < 0.1) continue;
|
||||
if (df[i] <= df[i-1] * 1.1) continue;
|
||||
if (df[i] < 0.22) continue;
|
||||
|
||||
if (!hardPeakCandidates.empty() &&
|
||||
i < prevHardPeak + hardPeakAmnesty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hard = (df[i] > 0.4);
|
||||
|
||||
if (hard && (m_debugLevel > 1)) {
|
||||
std::cerr << "hard peak at " << i << ": " << df[i]
|
||||
<< " > absolute " << 0.4
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (!hard) {
|
||||
hard = (df[i] > df[i-1] * 1.4);
|
||||
|
||||
if (hard && (m_debugLevel > 1)) {
|
||||
std::cerr << "hard peak at " << i << ": " << df[i]
|
||||
<< " > prev " << df[i-1] << " * 1.4"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hard && i > 1) {
|
||||
hard = (df[i] > df[i-1] * 1.2 &&
|
||||
df[i-1] > df[i-2] * 1.2);
|
||||
|
||||
if (hard && (m_debugLevel > 1)) {
|
||||
std::cerr << "hard peak at " << i << ": " << df[i]
|
||||
<< " > prev " << df[i-1] << " * 1.2 and "
|
||||
<< df[i-1] << " > prev " << df[i-2] << " * 1.2"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hard && i > 2) {
|
||||
// have already established that df[i] > df[i-1] * 1.1
|
||||
hard = (df[i] > 0.3 &&
|
||||
df[i-1] > df[i-2] * 1.1 &&
|
||||
df[i-2] > df[i-3] * 1.1);
|
||||
|
||||
if (hard && (m_debugLevel > 1)) {
|
||||
std::cerr << "hard peak at " << i << ": " << df[i]
|
||||
<< " > prev " << df[i-1] << " * 1.1 and "
|
||||
<< df[i-1] << " > prev " << df[i-2] << " * 1.1 and "
|
||||
<< df[i-2] << " > prev " << df[i-3] << " * 1.1"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hard) continue;
|
||||
|
||||
// (df[i+1] > df[i] && df[i+1] > df[i-1] * 1.8) ||
|
||||
// df[i] > 0.4) {
|
||||
|
||||
size_t peakLocation = i;
|
||||
|
||||
if (i + 1 < rawDf.size() &&
|
||||
rawDf[i + 1] > rawDf[i] * 1.4) {
|
||||
|
||||
++peakLocation;
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "pushing hard peak forward to " << peakLocation << ": " << df[peakLocation] << " > " << df[peakLocation-1] << " * " << 1.4 << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
hardPeakCandidates.insert(peakLocation);
|
||||
prevHardPeak = peakLocation;
|
||||
}
|
||||
}
|
||||
|
||||
size_t medianmaxsize = lrint(ceil(double(m_sampleRate) /
|
||||
double(m_increment))); // 1 sec ish
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "mediansize = " << medianmaxsize << std::endl;
|
||||
}
|
||||
if (medianmaxsize < 7) {
|
||||
medianmaxsize = 7;
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "adjusted mediansize = " << medianmaxsize << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int minspacing = lrint(ceil(double(m_sampleRate) /
|
||||
(20 * double(m_increment)))); // 0.05 sec ish
|
||||
|
||||
std::deque<float> medianwin;
|
||||
std::vector<float> sorted;
|
||||
int softPeakAmnesty = 0;
|
||||
|
||||
for (size_t i = 0; i < medianmaxsize/2; ++i) {
|
||||
medianwin.push_back(0);
|
||||
}
|
||||
for (size_t i = 0; i < medianmaxsize/2 && i < df.size(); ++i) {
|
||||
medianwin.push_back(df[i]);
|
||||
}
|
||||
|
||||
size_t lastSoftPeak = 0;
|
||||
|
||||
for (size_t i = 0; i < df.size(); ++i) {
|
||||
|
||||
size_t mediansize = medianmaxsize;
|
||||
|
||||
if (medianwin.size() < mediansize) {
|
||||
mediansize = medianwin.size();
|
||||
}
|
||||
|
||||
size_t middle = medianmaxsize / 2;
|
||||
if (middle >= mediansize) middle = mediansize-1;
|
||||
|
||||
size_t nextDf = i + mediansize - middle;
|
||||
|
||||
if (mediansize < 2) {
|
||||
if (mediansize > medianmaxsize) { // absurd, but never mind that
|
||||
medianwin.pop_front();
|
||||
}
|
||||
if (nextDf < df.size()) {
|
||||
medianwin.push_back(df[nextDf]);
|
||||
} else {
|
||||
medianwin.push_back(0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_debugLevel > 2) {
|
||||
// std::cerr << "have " << mediansize << " in median buffer" << std::endl;
|
||||
}
|
||||
|
||||
sorted.clear();
|
||||
for (size_t j = 0; j < mediansize; ++j) {
|
||||
sorted.push_back(medianwin[j]);
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
|
||||
size_t n = 90; // percentile above which we pick peaks
|
||||
size_t index = (sorted.size() * n) / 100;
|
||||
if (index >= sorted.size()) index = sorted.size()-1;
|
||||
if (index == sorted.size()-1 && index > 0) --index;
|
||||
float thresh = sorted[index];
|
||||
|
||||
// if (m_debugLevel > 2) {
|
||||
// std::cerr << "medianwin[" << middle << "] = " << medianwin[middle] << ", thresh = " << thresh << std::endl;
|
||||
// if (medianwin[middle] == 0.f) {
|
||||
// std::cerr << "contents: ";
|
||||
// for (size_t j = 0; j < medianwin.size(); ++j) {
|
||||
// std::cerr << medianwin[j] << " ";
|
||||
// }
|
||||
// std::cerr << std::endl;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (medianwin[middle] > thresh &&
|
||||
medianwin[middle] > medianwin[middle-1] &&
|
||||
medianwin[middle] > medianwin[middle+1] &&
|
||||
softPeakAmnesty == 0) {
|
||||
|
||||
size_t maxindex = middle;
|
||||
float maxval = medianwin[middle];
|
||||
|
||||
for (size_t j = middle+1; j < mediansize; ++j) {
|
||||
if (medianwin[j] > maxval) {
|
||||
maxval = medianwin[j];
|
||||
maxindex = j;
|
||||
} else if (medianwin[j] < medianwin[middle]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t peak = i + maxindex - middle;
|
||||
|
||||
// std::cerr << "i = " << i << ", maxindex = " << maxindex << ", middle = " << middle << ", so peak at " << peak << std::endl;
|
||||
|
||||
if (softPeakCandidates.empty() || lastSoftPeak != peak) {
|
||||
|
||||
if (m_debugLevel > 1) {
|
||||
std::cerr << "soft peak at " << peak << " ("
|
||||
<< peak * m_increment << "): "
|
||||
<< medianwin[middle] << " > "
|
||||
<< thresh << " and "
|
||||
<< medianwin[middle]
|
||||
<< " > " << medianwin[middle-1] << " and "
|
||||
<< medianwin[middle]
|
||||
<< " > " << medianwin[middle+1]
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (peak >= df.size()) {
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "peak is beyond end" << std::endl;
|
||||
}
|
||||
} else {
|
||||
softPeakCandidates.insert(peak);
|
||||
lastSoftPeak = peak;
|
||||
}
|
||||
}
|
||||
|
||||
softPeakAmnesty = minspacing + maxindex - middle;
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "amnesty = " << softPeakAmnesty << std::endl;
|
||||
}
|
||||
|
||||
} else if (softPeakAmnesty > 0) --softPeakAmnesty;
|
||||
|
||||
if (mediansize >= medianmaxsize) {
|
||||
medianwin.pop_front();
|
||||
}
|
||||
if (nextDf < df.size()) {
|
||||
medianwin.push_back(df[nextDf]);
|
||||
} else {
|
||||
medianwin.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Peak> peaks;
|
||||
|
||||
while (!hardPeakCandidates.empty() || !softPeakCandidates.empty()) {
|
||||
|
||||
bool haveHardPeak = !hardPeakCandidates.empty();
|
||||
bool haveSoftPeak = !softPeakCandidates.empty();
|
||||
|
||||
size_t hardPeak = (haveHardPeak ? *hardPeakCandidates.begin() : 0);
|
||||
size_t softPeak = (haveSoftPeak ? *softPeakCandidates.begin() : 0);
|
||||
|
||||
Peak peak;
|
||||
peak.hard = false;
|
||||
peak.chunk = softPeak;
|
||||
|
||||
bool ignore = false;
|
||||
|
||||
if (haveHardPeak &&
|
||||
(!haveSoftPeak || hardPeak <= softPeak)) {
|
||||
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "Hard peak: " << hardPeak << std::endl;
|
||||
}
|
||||
|
||||
peak.hard = true;
|
||||
peak.chunk = hardPeak;
|
||||
hardPeakCandidates.erase(hardPeakCandidates.begin());
|
||||
|
||||
} else {
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "Soft peak: " << softPeak << std::endl;
|
||||
}
|
||||
if (!peaks.empty() &&
|
||||
peaks[peaks.size()-1].hard &&
|
||||
peaks[peaks.size()-1].chunk + 3 >= softPeak) {
|
||||
if (m_debugLevel > 2) {
|
||||
std::cerr << "(ignoring, as we just had a hard peak)"
|
||||
<< std::endl;
|
||||
}
|
||||
ignore = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveSoftPeak && peak.chunk == softPeak) {
|
||||
softPeakCandidates.erase(softPeakCandidates.begin());
|
||||
}
|
||||
|
||||
if (!ignore) {
|
||||
peaks.push_back(peak);
|
||||
}
|
||||
}
|
||||
|
||||
return peaks;
|
||||
}
|
||||
|
||||
std::vector<float>
|
||||
StretchCalculator::smoothDF(const std::vector<float> &df)
|
||||
{
|
||||
std::vector<float> smoothedDF;
|
||||
|
||||
for (size_t i = 0; i < df.size(); ++i) {
|
||||
// three-value moving mean window for simple smoothing
|
||||
float total = 0.f, count = 0;
|
||||
if (i > 0) { total += df[i-1]; ++count; }
|
||||
total += df[i]; ++count;
|
||||
if (i+1 < df.size()) { total += df[i+1]; ++count; }
|
||||
float mean = total / count;
|
||||
smoothedDF.push_back(mean);
|
||||
}
|
||||
|
||||
return smoothedDF;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
115
src/faster/StretchCalculator.h
Normal file
115
src/faster/StretchCalculator.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_STRETCH_CALCULATOR_H
|
||||
#define RUBBERBAND_STRETCH_CALCULATOR_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class StretchCalculator
|
||||
{
|
||||
public:
|
||||
StretchCalculator(size_t sampleRate, size_t inputIncrement, bool useHardPeaks);
|
||||
virtual ~StretchCalculator();
|
||||
|
||||
/**
|
||||
* Provide a set of mappings from "before" to "after" sample
|
||||
* numbers so as to enforce a particular stretch profile. This
|
||||
* must be called before calculate(). The argument is a map from
|
||||
* audio sample frame number in the source material to the
|
||||
* corresponding sample frame number in the stretched output.
|
||||
*/
|
||||
void setKeyFrameMap(const std::map<size_t, size_t> &mapping);
|
||||
|
||||
/**
|
||||
* Calculate phase increments for a region of audio, given the
|
||||
* overall target stretch ratio, input duration in audio samples,
|
||||
* and the audio curves to use for identifying phase lock points.
|
||||
*/
|
||||
std::vector<int> calculate(double ratio, size_t inputDuration,
|
||||
const std::vector<float> &lockAudioCurve);
|
||||
|
||||
/**
|
||||
* Calculate the phase increment for a single audio block, given
|
||||
* the overall target stretch ratio and the block's value on the
|
||||
* phase-lock audio curve. State is retained between calls in the
|
||||
* StretchCalculator object; call reset() to reset it. This uses
|
||||
* a less sophisticated method than the offline calculate().
|
||||
*
|
||||
* If increment is non-zero, use it for the input increment for
|
||||
* this block in preference to m_increment.
|
||||
*/
|
||||
int calculateSingle(double timeRatio,
|
||||
double effectivePitchRatio,
|
||||
float curveValue,
|
||||
size_t increment,
|
||||
size_t analysisWindowSize,
|
||||
size_t synthesisWindowSize);
|
||||
|
||||
void setUseHardPeaks(bool use) { m_useHardPeaks = use; }
|
||||
|
||||
void reset();
|
||||
|
||||
void setDebugLevel(int level) { m_debugLevel = level; }
|
||||
|
||||
struct Peak {
|
||||
size_t chunk;
|
||||
bool hard;
|
||||
};
|
||||
std::vector<Peak> getLastCalculatedPeaks() const { return m_peaks; }
|
||||
|
||||
std::vector<float> smoothDF(const std::vector<float> &df);
|
||||
|
||||
protected:
|
||||
std::vector<Peak> findPeaks(const std::vector<float> &audioCurve);
|
||||
|
||||
void mapPeaks(std::vector<Peak> &peaks, std::vector<size_t> &targets,
|
||||
size_t outputDuration, size_t totalCount);
|
||||
|
||||
size_t m_sampleRate;
|
||||
size_t m_increment;
|
||||
float m_prevDf;
|
||||
double m_prevRatio;
|
||||
double m_prevTimeRatio;
|
||||
int m_transientAmnesty; // only in RT mode; handled differently offline
|
||||
int m_debugLevel;
|
||||
bool m_useHardPeaks;
|
||||
int64_t m_inFrameCounter;
|
||||
std::pair<int64_t, int64_t> m_frameCheckpoint;
|
||||
int64_t expectedOutFrame(int64_t inFrame, double timeRatio);
|
||||
double m_outFrameCounter;
|
||||
|
||||
std::map<size_t, size_t> m_keyFrameMap;
|
||||
std::vector<Peak> m_peaks;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
291
src/faster/StretcherChannelData.cpp
Normal file
291
src/faster/StretcherChannelData.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#include "StretcherChannelData.h"
|
||||
|
||||
#include "../common/Resampler.h"
|
||||
#include "../common/Allocators.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
RubberBandStretcher::Impl::ChannelData::ChannelData(size_t windowSize,
|
||||
size_t fftSize,
|
||||
size_t outbufSize)
|
||||
{
|
||||
std::set<size_t> s;
|
||||
construct(s, windowSize, fftSize, outbufSize);
|
||||
}
|
||||
|
||||
RubberBandStretcher::Impl::ChannelData::ChannelData(const std::set<size_t> &sizes,
|
||||
size_t initialWindowSize,
|
||||
size_t initialFftSize,
|
||||
size_t outbufSize)
|
||||
{
|
||||
construct(sizes, initialWindowSize, initialFftSize, outbufSize);
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::ChannelData::construct(const std::set<size_t> &sizes,
|
||||
size_t initialWindowSize,
|
||||
size_t initialFftSize,
|
||||
size_t outbufSize)
|
||||
{
|
||||
size_t maxSize = initialWindowSize * 2;
|
||||
if (initialFftSize > maxSize) maxSize = initialFftSize;
|
||||
|
||||
// std::cerr << "ChannelData::construct: initialWindowSize = " << initialWindowSize << ", initialFftSize = " << initialFftSize << ", outbufSize = " << outbufSize << std::endl;
|
||||
|
||||
// std::set is ordered by value
|
||||
std::set<size_t>::const_iterator i = sizes.end();
|
||||
if (i != sizes.begin()) {
|
||||
--i;
|
||||
if (*i > maxSize) maxSize = *i;
|
||||
}
|
||||
|
||||
// max possible size of the real "half" of freq data
|
||||
size_t realSize = maxSize / 2 + 1;
|
||||
|
||||
// std::cerr << "ChannelData::construct([" << sizes.size() << "], " << maxSize << ", " << realSize << ", " << outbufSize << ")" << std::endl;
|
||||
|
||||
if (outbufSize < maxSize) outbufSize = maxSize;
|
||||
|
||||
inbuf = new RingBuffer<float>(maxSize);
|
||||
outbuf = new RingBuffer<float>(outbufSize);
|
||||
|
||||
mag = allocate_and_zero<process_t>(realSize);
|
||||
phase = allocate_and_zero<process_t>(realSize);
|
||||
prevPhase = allocate_and_zero<process_t>(realSize);
|
||||
prevError = allocate_and_zero<process_t>(realSize);
|
||||
unwrappedPhase = allocate_and_zero<process_t>(realSize);
|
||||
envelope = allocate_and_zero<process_t>(realSize);
|
||||
|
||||
fltbuf = allocate_and_zero<float>(maxSize);
|
||||
dblbuf = allocate_and_zero<process_t>(maxSize);
|
||||
|
||||
accumulator = allocate_and_zero<float>(maxSize);
|
||||
windowAccumulator = allocate_and_zero<float>(maxSize);
|
||||
ms = allocate_and_zero<float>(maxSize);
|
||||
interpolator = allocate_and_zero<float>(maxSize);
|
||||
interpolatorScale = 0;
|
||||
|
||||
for (std::set<size_t>::const_iterator i = sizes.begin();
|
||||
i != sizes.end(); ++i) {
|
||||
ffts[*i] = new FFT(*i);
|
||||
if (sizeof(process_t) == sizeof(double)) {
|
||||
ffts[*i]->initDouble();
|
||||
} else {
|
||||
ffts[*i]->initFloat();
|
||||
}
|
||||
}
|
||||
fft = ffts[initialFftSize];
|
||||
|
||||
resampler = 0;
|
||||
resamplebuf = 0;
|
||||
resamplebufSize = 0;
|
||||
|
||||
reset();
|
||||
|
||||
// Avoid dividing opening sample (which will be discarded anyway) by zero
|
||||
windowAccumulator[0] = 1.f;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::ChannelData::setSizes(size_t windowSize,
|
||||
size_t fftSize)
|
||||
{
|
||||
// std::cerr << "ChannelData::setSizes: windowSize = " << windowSize << ", fftSize = " << fftSize << std::endl;
|
||||
|
||||
size_t maxSize = 2 * std::max(windowSize, fftSize);
|
||||
size_t realSize = maxSize / 2 + 1;
|
||||
size_t oldMax = inbuf->getSize();
|
||||
size_t oldReal = oldMax / 2 + 1;
|
||||
|
||||
if (oldMax >= maxSize) {
|
||||
|
||||
// no need to reallocate buffers, just reselect fft
|
||||
|
||||
//!!! we can't actually do this without locking against the
|
||||
//process thread, can we? we need to zero the mag/phase
|
||||
//buffers without interference
|
||||
|
||||
if (ffts.find(fftSize) == ffts.end()) {
|
||||
//!!! this also requires a lock, but it shouldn't occur in
|
||||
//RT mode with proper initialisation
|
||||
ffts[fftSize] = new FFT(fftSize);
|
||||
if (sizeof(process_t) == sizeof(double)) {
|
||||
ffts[fftSize]->initDouble();
|
||||
} else {
|
||||
ffts[fftSize]->initFloat();
|
||||
}
|
||||
}
|
||||
|
||||
fft = ffts[fftSize];
|
||||
|
||||
v_zero(fltbuf, maxSize);
|
||||
v_zero(dblbuf, maxSize);
|
||||
|
||||
v_zero(mag, realSize);
|
||||
v_zero(phase, realSize);
|
||||
v_zero(prevPhase, realSize);
|
||||
v_zero(prevError, realSize);
|
||||
v_zero(unwrappedPhase, realSize);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//!!! at this point we need a lock in case a different client
|
||||
//thread is calling process() -- we need this lock even if we
|
||||
//aren't running in threaded mode ourselves -- if we're in RT
|
||||
//mode, then the process call should trylock and fail if the lock
|
||||
//is unavailable (since this should never normally be the case in
|
||||
//general use in RT mode)
|
||||
|
||||
RingBuffer<float> *newbuf = inbuf->resized(maxSize);
|
||||
delete inbuf;
|
||||
inbuf = newbuf;
|
||||
|
||||
// We don't want to preserve data in these arrays
|
||||
|
||||
mag = reallocate_and_zero(mag, oldReal, realSize);
|
||||
phase = reallocate_and_zero(phase, oldReal, realSize);
|
||||
prevPhase = reallocate_and_zero(prevPhase, oldReal, realSize);
|
||||
prevError = reallocate_and_zero(prevError, oldReal, realSize);
|
||||
unwrappedPhase = reallocate_and_zero(unwrappedPhase, oldReal, realSize);
|
||||
envelope = reallocate_and_zero(envelope, oldReal, realSize);
|
||||
fltbuf = reallocate_and_zero(fltbuf, oldMax, maxSize);
|
||||
dblbuf = reallocate_and_zero(dblbuf, oldMax, maxSize);
|
||||
ms = reallocate_and_zero(ms, oldMax, maxSize);
|
||||
interpolator = reallocate_and_zero(interpolator, oldMax, maxSize);
|
||||
|
||||
// But we do want to preserve data in these
|
||||
|
||||
accumulator = reallocate_and_zero_extension
|
||||
(accumulator, oldMax, maxSize);
|
||||
|
||||
windowAccumulator = reallocate_and_zero_extension
|
||||
(windowAccumulator, oldMax, maxSize);
|
||||
|
||||
interpolatorScale = 0;
|
||||
|
||||
//!!! and resampler?
|
||||
|
||||
if (ffts.find(fftSize) == ffts.end()) {
|
||||
ffts[fftSize] = new FFT(fftSize);
|
||||
if (sizeof(process_t) == sizeof(double)) {
|
||||
ffts[fftSize]->initDouble();
|
||||
} else {
|
||||
ffts[fftSize]->initFloat();
|
||||
}
|
||||
}
|
||||
|
||||
fft = ffts[fftSize];
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::ChannelData::setOutbufSize(size_t outbufSize)
|
||||
{
|
||||
size_t oldSize = outbuf->getSize();
|
||||
|
||||
// std::cerr << "ChannelData::setOutbufSize(" << outbufSize << ") [from " << oldSize << "]" << std::endl;
|
||||
|
||||
if (oldSize < outbufSize) {
|
||||
|
||||
//!!! at this point we need a lock in case a different client
|
||||
//thread is calling process()
|
||||
|
||||
RingBuffer<float> *newbuf = outbuf->resized(outbufSize);
|
||||
delete outbuf;
|
||||
outbuf = newbuf;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::ChannelData::setResampleBufSize(size_t sz)
|
||||
{
|
||||
resamplebuf = reallocate_and_zero<float>(resamplebuf, resamplebufSize, sz);
|
||||
resamplebufSize = sz;
|
||||
}
|
||||
|
||||
RubberBandStretcher::Impl::ChannelData::~ChannelData()
|
||||
{
|
||||
delete resampler;
|
||||
|
||||
deallocate(resamplebuf);
|
||||
|
||||
delete inbuf;
|
||||
delete outbuf;
|
||||
|
||||
deallocate(mag);
|
||||
deallocate(phase);
|
||||
deallocate(prevPhase);
|
||||
deallocate(prevError);
|
||||
deallocate(unwrappedPhase);
|
||||
deallocate(envelope);
|
||||
deallocate(interpolator);
|
||||
deallocate(ms);
|
||||
deallocate(accumulator);
|
||||
deallocate(windowAccumulator);
|
||||
deallocate(fltbuf);
|
||||
deallocate(dblbuf);
|
||||
|
||||
for (std::map<size_t, FFT *>::iterator i = ffts.begin();
|
||||
i != ffts.end(); ++i) {
|
||||
delete i->second;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RubberBandStretcher::Impl::ChannelData::reset()
|
||||
{
|
||||
inbuf->reset();
|
||||
outbuf->reset();
|
||||
|
||||
if (resampler) resampler->reset();
|
||||
|
||||
size_t size = inbuf->getSize();
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
accumulator[i] = 0.f;
|
||||
windowAccumulator[i] = 0.f;
|
||||
}
|
||||
|
||||
// Avoid dividing opening sample (which will be discarded anyway) by zero
|
||||
windowAccumulator[0] = 1.f;
|
||||
|
||||
accumulatorFill = 0;
|
||||
prevIncrement = 0;
|
||||
chunkCount = 0;
|
||||
inCount = 0;
|
||||
inputSize = -1;
|
||||
outCount = 0;
|
||||
interpolatorScale = 0;
|
||||
unchanged = true;
|
||||
draining = false;
|
||||
outputComplete = false;
|
||||
}
|
||||
|
||||
}
|
||||
147
src/faster/StretcherChannelData.h
Normal file
147
src/faster/StretcherChannelData.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_STRETCHERCHANNELDATA_H
|
||||
#define RUBBERBAND_STRETCHERCHANNELDATA_H
|
||||
|
||||
#include "StretcherImpl.h"
|
||||
|
||||
#include <set>
|
||||
#include <atomic>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
class Resampler;
|
||||
|
||||
class RubberBandStretcher::Impl::ChannelData
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a ChannelData structure.
|
||||
*
|
||||
* The sizes passed in here are for the time-domain analysis
|
||||
* window and FFT calculation, and most of the buffer sizes also
|
||||
* depend on them. In practice they are always powers of two, the
|
||||
* window and FFT sizes are either equal or generally in a 2:1
|
||||
* relationship either way, and except for very extreme stretches
|
||||
* the FFT size is either 1024, 2048 or 4096.
|
||||
*
|
||||
* The outbuf size depends on other factors as well, including
|
||||
* the pitch scale factor and any maximum processing block
|
||||
* size specified by the user of the code.
|
||||
*/
|
||||
ChannelData(size_t windowSize,
|
||||
size_t fftSize,
|
||||
size_t outbufSize);
|
||||
|
||||
/**
|
||||
* Construct a ChannelData structure that can process at different
|
||||
* FFT sizes without requiring reallocation when the size changes.
|
||||
* The sizes can subsequently be changed with a call to setSizes.
|
||||
* Reallocation will only be necessary if setSizes is called with
|
||||
* values not equal to any of those passed in to the constructor.
|
||||
*
|
||||
* The outbufSize should be the maximum possible outbufSize to
|
||||
* avoid reallocation, which will happen if setOutbufSize is
|
||||
* called subsequently.
|
||||
*/
|
||||
ChannelData(const std::set<size_t> &sizes,
|
||||
size_t initialWindowSize,
|
||||
size_t initialFftSize,
|
||||
size_t outbufSize);
|
||||
~ChannelData();
|
||||
|
||||
/**
|
||||
* Reset buffers
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Set the FFT, analysis window, and buffer sizes. If this
|
||||
* ChannelData was constructed with a set of sizes and the given
|
||||
* window and FFT sizes here were among them, no reallocation will
|
||||
* be required.
|
||||
*/
|
||||
void setSizes(size_t windowSize, size_t fftSizes);
|
||||
|
||||
/**
|
||||
* Set the outbufSize for the channel data. Reallocation will
|
||||
* occur.
|
||||
*/
|
||||
void setOutbufSize(size_t outbufSize);
|
||||
|
||||
/**
|
||||
* Set the resampler buffer size. Default if not called is no
|
||||
* buffer allocated at all.
|
||||
*/
|
||||
void setResampleBufSize(size_t resamplebufSize);
|
||||
|
||||
RingBuffer<float> *inbuf;
|
||||
RingBuffer<float> *outbuf;
|
||||
|
||||
process_t *mag;
|
||||
process_t *phase;
|
||||
|
||||
process_t *prevPhase;
|
||||
process_t *prevError;
|
||||
process_t *unwrappedPhase;
|
||||
|
||||
float *accumulator;
|
||||
size_t accumulatorFill;
|
||||
float *windowAccumulator;
|
||||
float *ms; // only used when mid-side processing
|
||||
float *interpolator; // only used when time-domain smoothing is on
|
||||
int interpolatorScale;
|
||||
|
||||
float *fltbuf;
|
||||
process_t *dblbuf; // owned by FFT object, only used for time domain FFT i/o
|
||||
process_t *envelope; // for cepstral formant shift
|
||||
bool unchanged;
|
||||
|
||||
size_t prevIncrement; // only used in RT mode
|
||||
|
||||
size_t chunkCount;
|
||||
size_t inCount;
|
||||
std::atomic<int64_t> inputSize; // set only after known (when data ended); -1 previously
|
||||
size_t outCount;
|
||||
|
||||
std::atomic<bool> draining;
|
||||
std::atomic<bool> outputComplete;
|
||||
|
||||
FFT *fft;
|
||||
std::map<size_t, FFT *> ffts;
|
||||
|
||||
Resampler *resampler;
|
||||
float *resamplebuf;
|
||||
size_t resamplebufSize;
|
||||
|
||||
private:
|
||||
void construct(const std::set<size_t> &sizes,
|
||||
size_t initialWindowSize, size_t initialFftSize,
|
||||
size_t outbufSize);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1341
src/faster/StretcherImpl.cpp
Normal file
1341
src/faster/StretcherImpl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
266
src/faster/StretcherImpl.h
Normal file
266
src/faster/StretcherImpl.h
Normal file
@@ -0,0 +1,266 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
||||
|
||||
/*
|
||||
Rubber Band Library
|
||||
An audio time-stretching and pitch-shifting library.
|
||||
Copyright 2007-2022 Particular Programs Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version. See the file
|
||||
COPYING included with this distribution for more information.
|
||||
|
||||
Alternatively, if you have a valid commercial licence for the
|
||||
Rubber Band Library obtained by agreement with the copyright
|
||||
holders, you may redistribute and/or modify it under the terms
|
||||
described in that licence.
|
||||
|
||||
If you wish to distribute code using the Rubber Band Library
|
||||
under terms other than those of the GNU General Public License,
|
||||
you must obtain a valid commercial licence before doing so.
|
||||
*/
|
||||
|
||||
#ifndef RUBBERBAND_STRETCHERIMPL_H
|
||||
#define RUBBERBAND_STRETCHERIMPL_H
|
||||
|
||||
#include "../rubberband/RubberBandStretcher.h"
|
||||
|
||||
#include "../common/Window.h"
|
||||
#include "../common/FFT.h"
|
||||
#include "../common/RingBuffer.h"
|
||||
#include "../common/Scavenger.h"
|
||||
#include "../common/Thread.h"
|
||||
#include "../common/sysutils.h"
|
||||
|
||||
#include "SincWindow.h"
|
||||
#include "CompoundAudioCurve.h"
|
||||
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace RubberBand
|
||||
{
|
||||
|
||||
#ifdef PROCESS_SAMPLE_TYPE
|
||||
typedef PROCESS_SAMPLE_TYPE process_t;
|
||||
#else
|
||||
typedef double process_t;
|
||||
#endif
|
||||
|
||||
class AudioCurveCalculator;
|
||||
class StretchCalculator;
|
||||
|
||||
class RubberBandStretcher::Impl
|
||||
{
|
||||
public:
|
||||
Impl(size_t sampleRate, size_t channels, Options options,
|
||||
double initialTimeRatio, double initialPitchScale);
|
||||
~Impl();
|
||||
|
||||
void reset();
|
||||
void setTimeRatio(double ratio);
|
||||
void setPitchScale(double scale);
|
||||
|
||||
double getTimeRatio() const;
|
||||
double getPitchScale() const;
|
||||
|
||||
size_t getLatency() const;
|
||||
|
||||
void setTransientsOption(Options);
|
||||
void setDetectorOption(Options);
|
||||
void setPhaseOption(Options);
|
||||
void setFormantOption(Options);
|
||||
void setPitchOption(Options);
|
||||
|
||||
void setExpectedInputDuration(size_t samples);
|
||||
void setMaxProcessSize(size_t samples);
|
||||
void setKeyFrameMap(const std::map<size_t, size_t> &);
|
||||
|
||||
size_t getSamplesRequired() const;
|
||||
|
||||
void study(const float *const *input, size_t samples, bool final);
|
||||
void process(const float *const *input, size_t samples, bool final);
|
||||
|
||||
int available() const;
|
||||
size_t retrieve(float *const *output, size_t samples) const;
|
||||
|
||||
float getFrequencyCutoff(int n) const;
|
||||
void setFrequencyCutoff(int n, float f);
|
||||
|
||||
size_t getInputIncrement() const {
|
||||
return m_increment;
|
||||
}
|
||||
|
||||
std::vector<int> getOutputIncrements() const;
|
||||
std::vector<float> getPhaseResetCurve() const;
|
||||
std::vector<int> getExactTimePoints() const;
|
||||
|
||||
size_t getChannelCount() const {
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
void calculateStretch();
|
||||
|
||||
void setDebugLevel(int level);
|
||||
static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; }
|
||||
|
||||
protected:
|
||||
size_t m_sampleRate;
|
||||
size_t m_channels;
|
||||
|
||||
void prepareChannelMS(size_t channel, const float *const *inputs,
|
||||
size_t offset, size_t samples, float *prepared);
|
||||
size_t consumeChannel(size_t channel, const float *const *inputs,
|
||||
size_t offset, size_t samples, bool final);
|
||||
void processChunks(size_t channel, bool &any, bool &last);
|
||||
bool processOneChunk(); // across all channels, for real time use
|
||||
bool processChunkForChannel(size_t channel, size_t phaseIncrement,
|
||||
size_t shiftIncrement, bool phaseReset);
|
||||
bool testInbufReadSpace(size_t channel);
|
||||
void calculateIncrements(size_t &phaseIncrement,
|
||||
size_t &shiftIncrement, bool &phaseReset);
|
||||
bool getIncrements(size_t channel, size_t &phaseIncrement,
|
||||
size_t &shiftIncrement, bool &phaseReset);
|
||||
void analyseChunk(size_t channel);
|
||||
void modifyChunk(size_t channel, size_t outputIncrement, bool phaseReset);
|
||||
void formantShiftChunk(size_t channel);
|
||||
void synthesiseChunk(size_t channel, size_t shiftIncrement);
|
||||
void writeChunk(size_t channel, size_t shiftIncrement, bool last);
|
||||
|
||||
void calculateSizes();
|
||||
void configure();
|
||||
void reconfigure();
|
||||
|
||||
double getEffectiveRatio() const;
|
||||
|
||||
size_t roundUp(size_t value); // to next power of two
|
||||
|
||||
template <typename T, typename S>
|
||||
void cutShiftAndFold(T *target, int targetSize,
|
||||
S *src, // destructive to src
|
||||
Window<float> *window) {
|
||||
window->cut(src);
|
||||
const int windowSize = window->getSize();
|
||||
const int hs = targetSize / 2;
|
||||
if (windowSize == targetSize) {
|
||||
v_convert(target, src + hs, hs);
|
||||
v_convert(target + hs, src, hs);
|
||||
} else {
|
||||
v_zero(target, targetSize);
|
||||
int j = targetSize - windowSize/2;
|
||||
while (j < 0) j += targetSize;
|
||||
for (int i = 0; i < windowSize; ++i) {
|
||||
target[j] += src[i];
|
||||
if (++j == targetSize) j = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool resampleBeforeStretching() const;
|
||||
|
||||
double m_timeRatio;
|
||||
double m_pitchScale;
|
||||
|
||||
// n.b. either m_fftSize is an integer multiple of m_windowSize,
|
||||
// or vice versa
|
||||
size_t m_fftSize;
|
||||
size_t m_aWindowSize; //!!! or use m_awindow->getSize() throughout?
|
||||
size_t m_sWindowSize; //!!! or use m_swindow->getSize() throughout?
|
||||
size_t m_increment;
|
||||
size_t m_outbufSize;
|
||||
|
||||
size_t m_maxProcessSize;
|
||||
size_t m_expectedInputDuration;
|
||||
|
||||
#ifndef NO_THREADING
|
||||
bool m_threaded;
|
||||
#endif
|
||||
|
||||
bool m_realtime;
|
||||
Options m_options;
|
||||
int m_debugLevel;
|
||||
|
||||
enum ProcessMode {
|
||||
JustCreated,
|
||||
Studying,
|
||||
Processing,
|
||||
Finished
|
||||
};
|
||||
|
||||
ProcessMode m_mode;
|
||||
|
||||
std::map<size_t, Window<float> *> m_windows;
|
||||
std::map<size_t, SincWindow<float> *> m_sincs;
|
||||
Window<float> *m_awindow;
|
||||
SincWindow<float> *m_afilter;
|
||||
Window<float> *m_swindow;
|
||||
FFT *m_studyFFT;
|
||||
|
||||
#ifndef NO_THREADING
|
||||
Condition m_spaceAvailable;
|
||||
|
||||
class ProcessThread : public Thread
|
||||
{
|
||||
public:
|
||||
ProcessThread(Impl *s, size_t c);
|
||||
void run();
|
||||
void signalDataAvailable();
|
||||
void abandon();
|
||||
private:
|
||||
Impl *m_s;
|
||||
size_t m_channel;
|
||||
Condition m_dataAvailable;
|
||||
bool m_abandoning;
|
||||
};
|
||||
|
||||
mutable Mutex m_threadSetMutex;
|
||||
typedef std::set<ProcessThread *> ThreadSet;
|
||||
ThreadSet m_threadSet;
|
||||
|
||||
#if defined(HAVE_IPP) && !defined(NO_THREADING) && !defined(USE_BQRESAMPLER) && !defined(USE_SPEEX) && !defined(HAVE_LIBSAMPLERATE)
|
||||
// Exasperatingly, the IPP polyphase resampler does not appear to
|
||||
// be thread-safe as advertised -- a good reason to prefer any of
|
||||
// the alternatives
|
||||
#define STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED 1
|
||||
Mutex m_resamplerMutex;
|
||||
#endif
|
||||
#endif // ! NO_THREADING
|
||||
|
||||
size_t m_inputDuration;
|
||||
CompoundAudioCurve::Type m_detectorType;
|
||||
std::vector<float> m_phaseResetDf;
|
||||
std::vector<bool> m_silence;
|
||||
int m_silentHistory;
|
||||
|
||||
class ChannelData;
|
||||
std::vector<ChannelData *> m_channelData;
|
||||
|
||||
std::vector<int> m_outputIncrements;
|
||||
|
||||
mutable RingBuffer<int> m_lastProcessOutputIncrements;
|
||||
mutable RingBuffer<float> m_lastProcessPhaseResetDf;
|
||||
Scavenger<RingBuffer<float> > m_emergencyScavenger;
|
||||
|
||||
CompoundAudioCurve *m_phaseResetAudioCurve;
|
||||
AudioCurveCalculator *m_silentAudioCurve;
|
||||
StretchCalculator *m_stretchCalculator;
|
||||
|
||||
float m_freq0;
|
||||
float m_freq1;
|
||||
float m_freq2;
|
||||
|
||||
size_t m_baseFftSize;
|
||||
float m_rateMultiple;
|
||||
|
||||
void writeOutput(RingBuffer<float> &to, float *from,
|
||||
size_t qty, size_t &outCount, size_t theoreticalOut);
|
||||
|
||||
static int m_defaultDebugLevel;
|
||||
static const size_t m_defaultIncrement;
|
||||
static const size_t m_defaultFftSize;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1308
src/faster/StretcherProcess.cpp
Normal file
1308
src/faster/StretcherProcess.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user