audacia/src/WaveClip.cpp

1774 lines
54 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
WaveClip.cpp
?? Dominic Mazzoni
?? Markus Meyer
*******************************************************************//**
\class WaveClip
\brief This allows multiple clips to be a part of one WaveTrack.
*//****************************************************************//**
\class WaveCache
\brief Cache used with WaveClip to cache wave information (for drawing).
*//*******************************************************************/
#include "WaveClip.h"
#include <math.h>
#include <vector>
#include <wx/log.h>
#include "Sequence.h"
#include "Spectrum.h"
#include "Prefs.h"
#include "Envelope.h"
#include "Resample.h"
#include "WaveTrack.h"
#include "Profiler.h"
#include "InconsistencyException.h"
#include "UserException.h"
#include "prefs/SpectrogramSettings.h"
#include "widgets/ProgressDialog.h"
#ifdef _OPENMP
#include <omp.h>
#endif
class WaveCache {
public:
WaveCache()
: dirty(-1)
, start(-1)
, pps(0)
, rate(-1)
, where(0)
, min(0)
, max(0)
, rms(0)
, bl(0)
{
}
WaveCache(size_t len_, double pixelsPerSecond, double rate_, double t0, int dirty_)
: dirty(dirty_)
, len(len_)
, start(t0)
, pps(pixelsPerSecond)
, rate(rate_)
, where(1 + len)
, min(len)
, max(len)
, rms(len)
, bl(len)
{
}
~WaveCache()
{
}
int dirty;
const size_t len { 0 }; // counts pixels, not samples
const double start;
const double pps;
const int rate;
std::vector<sampleCount> where;
std::vector<float> min;
std::vector<float> max;
std::vector<float> rms;
std::vector<int> bl;
};
static void ComputeSpectrumUsingRealFFTf
(float * __restrict buffer, const FFTParam *hFFT,
const float * __restrict window, size_t len, float * __restrict out)
{
size_t i;
if(len > hFFT->Points * 2)
len = hFFT->Points * 2;
for(i = 0; i < len; i++)
buffer[i] *= window[i];
for( ; i < (hFFT->Points * 2); i++)
buffer[i] = 0; // zero pad as needed
RealFFTf(buffer, hFFT);
// Handle the (real-only) DC
float power = buffer[0] * buffer[0];
if(power <= 0)
out[0] = -160.0;
else
out[0] = 10.0 * log10f(power);
for(i = 1; i < hFFT->Points; i++) {
const int index = hFFT->BitReversed[i];
const float re = buffer[index], im = buffer[index + 1];
power = re * re + im * im;
if(power <= 0)
out[i] = -160.0;
else
out[i] = 10.0*log10f(power);
}
}
WaveClip::WaveClip(const SampleBlockFactoryPtr &factory,
sampleFormat format, int rate, int colourIndex)
{
mRate = rate;
mColourIndex = colourIndex;
mSequence = std::make_unique<Sequence>(factory, format);
mEnvelope = std::make_unique<Envelope>(true, 1e-7, 2.0, 1.0);
mWaveCache = std::make_unique<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(1);
}
WaveClip::WaveClip(const WaveClip& orig,
const SampleBlockFactoryPtr &factory,
bool copyCutlines)
{
// essentially a copy constructor - but you must pass in the
// current sample block factory, because we might be copying
// from one project to another
mOffset = orig.mOffset;
mRate = orig.mRate;
mColourIndex = orig.mColourIndex;
mSequence = std::make_unique<Sequence>(*orig.mSequence, factory);
mEnvelope = std::make_unique<Envelope>(*orig.mEnvelope);
mWaveCache = std::make_unique<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(1);
if ( copyCutlines )
for (const auto &clip: orig.mCutLines)
mCutLines.push_back
( std::make_unique<WaveClip>( *clip, factory, true ) );
mIsPlaceholder = orig.GetIsPlaceholder();
}
WaveClip::WaveClip(const WaveClip& orig,
const SampleBlockFactoryPtr &factory,
bool copyCutlines,
double t0, double t1)
{
// Copy only a range of the other WaveClip
mOffset = orig.mOffset;
mRate = orig.mRate;
mColourIndex = orig.mColourIndex;
mWaveCache = std::make_unique<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(1);
mIsPlaceholder = orig.GetIsPlaceholder();
sampleCount s0, s1;
orig.TimeToSamplesClip(t0, &s0);
orig.TimeToSamplesClip(t1, &s1);
mSequence = orig.mSequence->Copy(factory, s0, s1);
mEnvelope = std::make_unique<Envelope>(
*orig.mEnvelope,
mOffset + s0.as_double()/mRate,
mOffset + s1.as_double()/mRate
);
if ( copyCutlines )
// Copy cutline clips that fall in the range
for (const auto &ppClip : orig.mCutLines)
{
const WaveClip* clip = ppClip.get();
double cutlinePosition = orig.mOffset + clip->GetOffset();
if (cutlinePosition >= t0 && cutlinePosition <= t1)
{
auto newCutLine =
std::make_unique< WaveClip >( *clip, factory, true );
newCutLine->SetOffset( cutlinePosition - t0 );
mCutLines.push_back(std::move(newCutLine));
}
}
}
WaveClip::~WaveClip()
{
}
/*! @excsafety{No-fail} */
void WaveClip::SetOffset(double offset)
{
mOffset = offset;
mEnvelope->SetOffset(mOffset);
}
bool WaveClip::GetSamples(samplePtr buffer, sampleFormat format,
sampleCount start, size_t len, bool mayThrow) const
{
return mSequence->Get(buffer, format, start, len, mayThrow);
}
/*! @excsafety{Strong} */
void WaveClip::SetSamples(constSamplePtr buffer, sampleFormat format,
sampleCount start, size_t len)
{
// use Strong-guarantee
mSequence->SetSamples(buffer, format, start, len);
// use No-fail-guarantee
MarkChanged();
}
BlockArray* WaveClip::GetSequenceBlockArray()
{
return &mSequence->GetBlockArray();
}
const BlockArray* WaveClip::GetSequenceBlockArray() const
{
return &mSequence->GetBlockArray();
}
double WaveClip::GetStartTime() const
{
// JS: mOffset is the minimum value and it is returned; no clipping to 0
return mOffset;
}
double WaveClip::GetEndTime() const
{
auto numSamples = mSequence->GetNumSamples();
double maxLen = mOffset + (numSamples+mAppendBufferLen).as_double()/mRate;
// JS: calculated value is not the length;
// it is a maximum value and can be negative; no clipping to 0
return maxLen;
}
sampleCount WaveClip::GetStartSample() const
{
return sampleCount( floor(mOffset * mRate + 0.5) );
}
sampleCount WaveClip::GetEndSample() const
{
return GetStartSample() + mSequence->GetNumSamples();
}
sampleCount WaveClip::GetNumSamples() const
{
return mSequence->GetNumSamples();
}
// Bug 2288 allowed overlapping clips.
// This was a classic fencepost error.
// We are within the clip if start < t <= end.
// Note that BeforeClip and AfterClip must be consistent
// with this definition.
bool WaveClip::WithinClip(double t) const
{
auto ts = (sampleCount)floor(t * mRate + 0.5);
return ts > GetStartSample() && ts < GetEndSample() + mAppendBufferLen;
}
bool WaveClip::BeforeClip(double t) const
{
auto ts = (sampleCount)floor(t * mRate + 0.5);
return ts <= GetStartSample();
}
bool WaveClip::AfterClip(double t) const
{
auto ts = (sampleCount)floor(t * mRate + 0.5);
return ts >= GetEndSample() + mAppendBufferLen;
}
// A sample at time t could be in the clip, but
// a clip start at time t still be from a clip
// not overlapping this one, with this test.
bool WaveClip::IsClipStartAfterClip(double t) const
{
auto ts = (sampleCount)floor(t * mRate + 0.5);
return ts >= GetEndSample() + mAppendBufferLen;
}
///Delete the wave cache - force redraw. Thread-safe
void WaveClip::ClearWaveCache()
{
mWaveCache = std::make_unique<WaveCache>();
}
namespace {
inline
void findCorrection(const std::vector<sampleCount> &oldWhere, size_t oldLen,
size_t newLen,
double t0, double rate, double samplesPerPixel,
int &oldX0, double &correction)
{
// Mitigate the accumulation of location errors
// in copies of copies of ... of caches.
// Look at the loop that populates "where" below to understand this.
// Find the sample position that is the origin in the old cache.
const double oldWhere0 = oldWhere[1].as_double() - samplesPerPixel;
const double oldWhereLast = oldWhere0 + oldLen * samplesPerPixel;
// Find the length in samples of the old cache.
const double denom = oldWhereLast - oldWhere0;
// What sample would go in where[0] with no correction?
const double guessWhere0 = t0 * rate;
if ( // Skip if old and NEW are disjoint:
oldWhereLast <= guessWhere0 ||
guessWhere0 + newLen * samplesPerPixel <= oldWhere0 ||
// Skip unless denom rounds off to at least 1.
denom < 0.5)
{
// The computation of oldX0 in the other branch
// may underflow and the assertion would be violated.
oldX0 = oldLen;
correction = 0.0;
}
else
{
// What integer position in the old cache array does that map to?
// (even if it is out of bounds)
oldX0 = floor(0.5 + oldLen * (guessWhere0 - oldWhere0) / denom);
// What sample count would the old cache have put there?
const double where0 = oldWhere0 + double(oldX0) * samplesPerPixel;
// What correction is needed to align the NEW cache with the old?
const double correction0 = where0 - guessWhere0;
correction = std::max(-samplesPerPixel, std::min(samplesPerPixel, correction0));
wxASSERT(correction == correction0);
}
}
inline void
fillWhere(std::vector<sampleCount> &where, size_t len, double bias, double correction,
double t0, double rate, double samplesPerPixel)
{
// Be careful to make the first value non-negative
const double w0 = 0.5 + correction + bias + t0 * rate;
where[0] = sampleCount( std::max(0.0, floor(w0)) );
for (decltype(len) x = 1; x < len + 1; x++)
where[x] = sampleCount( floor(w0 + double(x) * samplesPerPixel) );
}
}
//
// Getting high-level data from the track for screen display and
// clipping calculations
//
bool WaveClip::GetWaveDisplay(WaveDisplay &display, double t0,
double pixelsPerSecond) const
{
const bool allocated = (display.where != 0);
const size_t numPixels = (int)display.width;
size_t p0 = 0; // least column requiring computation
size_t p1 = numPixels; // greatest column requiring computation, plus one
float *min;
float *max;
float *rms;
int *bl;
std::vector<sampleCount> *pWhere;
if (allocated) {
// assume ownWhere is filled.
min = &display.min[0];
max = &display.max[0];
rms = &display.rms[0];
bl = &display.bl[0];
pWhere = &display.ownWhere;
}
else {
const double tstep = 1.0 / pixelsPerSecond;
const double samplesPerPixel = mRate * tstep;
// Make a tolerant comparison of the pps values in this wise:
// accumulated difference of times over the number of pixels is less than
// a sample period.
const bool ppsMatch = mWaveCache &&
(fabs(tstep - 1.0 / mWaveCache->pps) * numPixels < (1.0 / mRate));
const bool match =
mWaveCache &&
ppsMatch &&
mWaveCache->len > 0 &&
mWaveCache->dirty == mDirty;
if (match &&
mWaveCache->start == t0 &&
mWaveCache->len >= numPixels) {
// Satisfy the request completely from the cache
display.min = &mWaveCache->min[0];
display.max = &mWaveCache->max[0];
display.rms = &mWaveCache->rms[0];
display.bl = &mWaveCache->bl[0];
display.where = &mWaveCache->where[0];
return true;
}
std::unique_ptr<WaveCache> oldCache(std::move(mWaveCache));
int oldX0 = 0;
double correction = 0.0;
size_t copyBegin = 0, copyEnd = 0;
if (match) {
findCorrection(oldCache->where, oldCache->len, numPixels,
t0, mRate, samplesPerPixel,
oldX0, correction);
// Remember our first pixel maps to oldX0 in the old cache,
// possibly out of bounds.
// For what range of pixels can data be copied?
copyBegin = std::min<size_t>(numPixels, std::max(0, -oldX0));
copyEnd = std::min<size_t>(numPixels, std::max(0,
(int)oldCache->len - oldX0
));
}
if (!(copyEnd > copyBegin))
oldCache.reset(0);
mWaveCache = std::make_unique<WaveCache>(numPixels, pixelsPerSecond, mRate, t0, mDirty);
min = &mWaveCache->min[0];
max = &mWaveCache->max[0];
rms = &mWaveCache->rms[0];
bl = &mWaveCache->bl[0];
pWhere = &mWaveCache->where;
fillWhere(*pWhere, numPixels, 0.0, correction,
t0, mRate, samplesPerPixel);
// The range of pixels we must fetch from the Sequence:
p0 = (copyBegin > 0) ? 0 : copyEnd;
p1 = (copyEnd >= numPixels) ? copyBegin : numPixels;
// Optimization: if the old cache is good and overlaps
// with the current one, re-use as much of the cache as
// possible
if (oldCache) {
// Copy what we can from the old cache.
const int length = copyEnd - copyBegin;
const size_t sizeFloats = length * sizeof(float);
const int srcIdx = (int)copyBegin + oldX0;
memcpy(&min[copyBegin], &oldCache->min[srcIdx], sizeFloats);
memcpy(&max[copyBegin], &oldCache->max[srcIdx], sizeFloats);
memcpy(&rms[copyBegin], &oldCache->rms[srcIdx], sizeFloats);
memcpy(&bl[copyBegin], &oldCache->bl[srcIdx], length * sizeof(int));
}
}
if (p1 > p0) {
// Cache was not used or did not satisfy the whole request
std::vector<sampleCount> &where = *pWhere;
/* handle values in the append buffer */
auto numSamples = mSequence->GetNumSamples();
auto a = p0;
// Not all of the required columns might be in the sequence.
// Some might be in the append buffer.
for (; a < p1; ++a) {
if (where[a + 1] > numSamples)
break;
}
// Handle the columns that land in the append buffer.
//compute the values that are outside the overlap from scratch.
if (a < p1) {
sampleFormat seqFormat = mSequence->GetSampleFormat();
bool didUpdate = false;
for(auto i = a; i < p1; i++) {
auto left = std::max(sampleCount{ 0 },
where[i] - numSamples);
auto right = std::min(sampleCount{ mAppendBufferLen },
where[i + 1] - numSamples);
//wxCriticalSectionLocker locker(mAppendCriticalSection);
if (right > left) {
Floats b;
float *pb{};
// left is nonnegative and at most mAppendBufferLen:
auto sLeft = left.as_size_t();
// The difference is at most mAppendBufferLen:
size_t len = ( right - left ).as_size_t();
if (seqFormat == floatSample)
pb = &((float *)mAppendBuffer.ptr())[sLeft];
else {
b.reinit(len);
pb = b.get();
SamplesToFloats(
mAppendBuffer.ptr() + sLeft * SAMPLE_SIZE(seqFormat),
seqFormat, pb, len);
}
float theMax, theMin, sumsq;
{
const float val = pb[0];
theMax = theMin = val;
sumsq = val * val;
}
for(decltype(len) j = 1; j < len; j++) {
const float val = pb[j];
theMax = std::max(theMax, val);
theMin = std::min(theMin, val);
sumsq += val * val;
}
min[i] = theMin;
max[i] = theMax;
rms[i] = (float)sqrt(sumsq / len);
bl[i] = 1; //for now just fake it.
didUpdate=true;
}
}
// Shrink the right end of the range to fetch from Sequence
if(didUpdate)
p1 = a;
}
// Done with append buffer, now fetch the rest of the cache miss
// from the sequence
if (p1 > p0) {
if (!mSequence->GetWaveDisplay(&min[p0],
&max[p0],
&rms[p0],
&bl[p0],
p1-p0,
&where[p0]))
{
return false;
}
}
}
if (!allocated) {
// Now report the results
display.min = min;
display.max = max;
display.rms = rms;
display.bl = bl;
display.where = &(*pWhere)[0];
}
return true;
}
namespace {
void ComputeSpectrogramGainFactors
(size_t fftLen, double rate, int frequencyGain, std::vector<float> &gainFactors)
{
if (frequencyGain > 0) {
// Compute a frequency-dependent gain factor
// scaled such that 1000 Hz gets a gain of 0dB
// This is the reciprocal of the bin number of 1000 Hz:
const double factor = ((double)rate / (double)fftLen) / 1000.0;
auto half = fftLen / 2;
gainFactors.reserve(half);
// Don't take logarithm of zero! Let bin 0 replicate the gain factor for bin 1.
gainFactors.push_back(frequencyGain*log10(factor));
for (decltype(half) x = 1; x < half; x++) {
gainFactors.push_back(frequencyGain*log10(factor * x));
}
}
}
}
bool SpecCache::Matches
(int dirty_, double pixelsPerSecond,
const SpectrogramSettings &settings, double rate) const
{
// Make a tolerant comparison of the pps values in this wise:
// accumulated difference of times over the number of pixels is less than
// a sample period.
const double tstep = 1.0 / pixelsPerSecond;
const bool ppsMatch =
(fabs(tstep - 1.0 / pps) * len < (1.0 / rate));
return
ppsMatch &&
dirty == dirty_ &&
windowType == settings.windowType &&
windowSize == settings.WindowSize() &&
zeroPaddingFactor == settings.ZeroPaddingFactor() &&
frequencyGain == settings.frequencyGain &&
algorithm == settings.algorithm;
}
bool SpecCache::CalculateOneSpectrum
(const SpectrogramSettings &settings,
WaveTrackCache &waveTrackCache,
const int xx, const sampleCount numSamples,
double offset, double rate, double pixelsPerSecond,
int lowerBoundX, int upperBoundX,
const std::vector<float> &gainFactors,
float* __restrict scratch, float* __restrict out) const
{
bool result = false;
const bool reassignment =
(settings.algorithm == SpectrogramSettings::algReassignment);
const size_t windowSizeSetting = settings.WindowSize();
sampleCount from;
// xx may be for a column that is out of the visible bounds, but only
// when we are calculating reassignment contributions that may cross into
// the visible area.
if (xx < 0)
from = sampleCount(
where[0].as_double() + xx * (rate / pixelsPerSecond)
);
else if (xx > (int)len)
from = sampleCount(
where[len].as_double() + (xx - len) * (rate / pixelsPerSecond)
);
else
from = where[xx];
const bool autocorrelation =
settings.algorithm == SpectrogramSettings::algPitchEAC;
const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
const size_t padding = (windowSizeSetting * (zeroPaddingFactorSetting - 1)) / 2;
const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
auto nBins = settings.NBins();
if (from < 0 || from >= numSamples) {
if (xx >= 0 && xx < (int)len) {
// Pixel column is out of bounds of the clip! Should not happen.
float *const results = &out[nBins * xx];
std::fill(results, results + nBins, 0.0f);
}
}
else {
// We can avoid copying memory when ComputeSpectrum is used below
bool copy = !autocorrelation || (padding > 0) || reassignment;
float *useBuffer = 0;
float *adj = scratch + padding;
{
auto myLen = windowSizeSetting;
// Take a window of the track centered at this sample.
from -= windowSizeSetting >> 1;
if (from < 0) {
// Near the start of the clip, pad left with zeroes as needed.
// from is at least -windowSize / 2
for (auto ii = from; ii < 0; ++ii)
*adj++ = 0;
myLen += from.as_long_long(); // add a negative
from = 0;
copy = true;
}
if (from + myLen >= numSamples) {
// Near the end of the clip, pad right with zeroes as needed.
// newlen is bounded by myLen:
auto newlen = ( numSamples - from ).as_size_t();
for (decltype(myLen) ii = newlen; ii < myLen; ++ii)
adj[ii] = 0;
myLen = newlen;
copy = true;
}
if (myLen > 0) {
useBuffer = (float*)(waveTrackCache.GetFloats(
sampleCount(
floor(0.5 + from.as_double() + offset * rate)
),
myLen,
// Don't throw in this drawing operation
false)
);
if (copy) {
if (useBuffer)
memcpy(adj, useBuffer, myLen * sizeof(float));
else
memset(adj, 0, myLen * sizeof(float));
}
}
}
if (copy || !useBuffer)
useBuffer = scratch;
if (autocorrelation) {
// not reassignment, xx is surely within bounds.
wxASSERT(xx >= 0);
float *const results = &out[nBins * xx];
// This function does not mutate useBuffer
ComputeSpectrum(useBuffer, windowSizeSetting, windowSizeSetting,
rate, results,
autocorrelation, settings.windowType);
}
else if (reassignment) {
static const double epsilon = 1e-16;
const auto hFFT = settings.hFFT.get();
float *const scratch2 = scratch + fftLen;
std::copy(scratch, scratch2, scratch2);
float *const scratch3 = scratch + 2 * fftLen;
std::copy(scratch, scratch2, scratch3);
{
const float *const window = settings.window.get();
for (size_t ii = 0; ii < fftLen; ++ii)
scratch[ii] *= window[ii];
RealFFTf(scratch, hFFT);
}
{
const float *const dWindow = settings.dWindow.get();
for (size_t ii = 0; ii < fftLen; ++ii)
scratch2[ii] *= dWindow[ii];
RealFFTf(scratch2, hFFT);
}
{
const float *const tWindow = settings.tWindow.get();
for (size_t ii = 0; ii < fftLen; ++ii)
scratch3[ii] *= tWindow[ii];
RealFFTf(scratch3, hFFT);
}
for (size_t ii = 0; ii < hFFT->Points; ++ii) {
const int index = hFFT->BitReversed[ii];
const float
denomRe = scratch[index],
denomIm = ii == 0 ? 0 : scratch[index + 1];
const double power = denomRe * denomRe + denomIm * denomIm;
if (power < epsilon)
// Avoid dividing by near-zero below
continue;
double freqCorrection;
{
const double multiplier = -(fftLen / (2.0f * M_PI));
const float
numRe = scratch2[index],
numIm = ii == 0 ? 0 : scratch2[index + 1];
// Find complex quotient --
// Which means, multiply numerator by conjugate of denominator,
// then divide by norm squared of denominator --
// Then just take its imaginary part.
const double
quotIm = (-numRe * denomIm + numIm * denomRe) / power;
// With appropriate multiplier, that becomes the correction of
// the frequency bin.
freqCorrection = multiplier * quotIm;
}
const int bin = (int)((int)ii + freqCorrection + 0.5f);
// Must check if correction takes bin out of bounds, above or below!
// bin is signed!
if (bin >= 0 && bin < (int)hFFT->Points) {
double timeCorrection;
{
const float
numRe = scratch3[index],
numIm = ii == 0 ? 0 : scratch3[index + 1];
// Find another complex quotient --
// Then just take its real part.
// The result has sample interval as unit.
timeCorrection =
(numRe * denomRe + numIm * denomIm) / power;
}
int correctedX = (floor(0.5 + xx + timeCorrection * pixelsPerSecond / rate));
if (correctedX >= lowerBoundX && correctedX < upperBoundX)
{
result = true;
// This is non-negative, because bin and correctedX are
auto ind = (int)nBins * correctedX + bin;
#ifdef _OPENMP
// This assignment can race if index reaches into another thread's bins.
// The probability of a race very low, so this carries little overhead,
// about 5% slower vs allowing it to race.
#pragma omp atomic update
#endif
out[ind] += power;
}
}
}
}
else {
// not reassignment, xx is surely within bounds.
wxASSERT(xx >= 0);
float *const results = &out[nBins * xx];
// Do the FFT. Note that useBuffer is multiplied by the window,
// and the window is initialized with leading and trailing zeroes
// when there is padding. Therefore we did not need to reinitialize
// the part of useBuffer in the padding zones.
// This function mutates useBuffer
ComputeSpectrumUsingRealFFTf
(useBuffer, settings.hFFT.get(), settings.window.get(), fftLen, results);
if (!gainFactors.empty()) {
// Apply a frequency-dependent gain factor
for (size_t ii = 0; ii < nBins; ++ii)
results[ii] += gainFactors[ii];
}
}
}
return result;
}
void SpecCache::Grow(size_t len_, const SpectrogramSettings& settings,
double pixelsPerSecond, double start_)
{
settings.CacheWindows();
// len columns, and so many rows, column-major.
// Don't take column literally -- this isn't pixel data yet, it's the
// raw data to be mapped onto the display.
freq.resize(len_ * settings.NBins());
// Sample counts corresponding to the columns, and to one past the end.
where.resize(len_ + 1);
len = len_;
algorithm = settings.algorithm;
pps = pixelsPerSecond;
start = start_;
windowType = settings.windowType;
windowSize = settings.WindowSize();
zeroPaddingFactor = settings.ZeroPaddingFactor();
frequencyGain = settings.frequencyGain;
}
void SpecCache::Populate
(const SpectrogramSettings &settings, WaveTrackCache &waveTrackCache,
int copyBegin, int copyEnd, size_t numPixels,
sampleCount numSamples,
double offset, double rate, double pixelsPerSecond)
{
const int &frequencyGainSetting = settings.frequencyGain;
const size_t windowSizeSetting = settings.WindowSize();
const bool autocorrelation =
settings.algorithm == SpectrogramSettings::algPitchEAC;
const bool reassignment =
settings.algorithm == SpectrogramSettings::algReassignment;
#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
#else
const size_t zeroPaddingFactorSetting = 1;
#endif
// FFT length may be longer than the window of samples that affect results
// because of zero padding done for increased frequency resolution
const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
const auto nBins = settings.NBins();
const size_t bufferSize = fftLen;
const size_t scratchSize = reassignment ? 3 * bufferSize : bufferSize;
std::vector<float> scratch(scratchSize);
std::vector<float> gainFactors;
if (!autocorrelation)
ComputeSpectrogramGainFactors(fftLen, rate, frequencyGainSetting, gainFactors);
// Loop over the ranges before and after the copied portion and compute anew.
// One of the ranges may be empty.
for (int jj = 0; jj < 2; ++jj) {
const int lowerBoundX = jj == 0 ? 0 : copyEnd;
const int upperBoundX = jj == 0 ? copyBegin : numPixels;
#ifdef _OPENMP
// Storage for mutable per-thread data.
// private clause ensures one copy per thread
struct ThreadLocalStorage {
ThreadLocalStorage() { }
~ThreadLocalStorage() { }
void init(WaveTrackCache &waveTrackCache, size_t scratchSize) {
if (!cache) {
cache = std::make_unique<WaveTrackCache>(waveTrackCache.GetTrack());
scratch.resize(scratchSize);
}
}
std::unique_ptr<WaveTrackCache> cache;
std::vector<float> scratch;
} tls;
#pragma omp parallel for private(tls)
#endif
for (auto xx = lowerBoundX; xx < upperBoundX; ++xx)
{
#ifdef _OPENMP
tls.init(waveTrackCache, scratchSize);
WaveTrackCache& cache = *tls.cache;
float* buffer = &tls.scratch[0];
#else
WaveTrackCache& cache = waveTrackCache;
float* buffer = &scratch[0];
#endif
CalculateOneSpectrum(
settings, cache, xx, numSamples,
offset, rate, pixelsPerSecond,
lowerBoundX, upperBoundX,
gainFactors, buffer, &freq[0]);
}
if (reassignment) {
// Need to look beyond the edges of the range to accumulate more
// time reassignments.
// I'm not sure what's a good stopping criterion?
auto xx = lowerBoundX;
const double pixelsPerSample = pixelsPerSecond / rate;
const int limit = std::min((int)(0.5 + fftLen * pixelsPerSample), 100);
for (int ii = 0; ii < limit; ++ii)
{
const bool result =
CalculateOneSpectrum(
settings, waveTrackCache, --xx, numSamples,
offset, rate, pixelsPerSecond,
lowerBoundX, upperBoundX,
gainFactors, &scratch[0], &freq[0]);
if (!result)
break;
}
xx = upperBoundX;
for (int ii = 0; ii < limit; ++ii)
{
const bool result =
CalculateOneSpectrum(
settings, waveTrackCache, xx++, numSamples,
offset, rate, pixelsPerSecond,
lowerBoundX, upperBoundX,
gainFactors, &scratch[0], &freq[0]);
if (!result)
break;
}
// Now Convert to dB terms. Do this only after accumulating
// power values, which may cross columns with the time correction.
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (xx = lowerBoundX; xx < upperBoundX; ++xx) {
float *const results = &freq[nBins * xx];
for (size_t ii = 0; ii < nBins; ++ii) {
float &power = results[ii];
if (power <= 0)
power = -160.0;
else
power = 10.0*log10f(power);
}
if (!gainFactors.empty()) {
// Apply a frequency-dependent gain factor
for (size_t ii = 0; ii < nBins; ++ii)
results[ii] += gainFactors[ii];
}
}
}
}
}
bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache,
const float *& spectrogram,
const sampleCount *& where,
size_t numPixels,
double t0, double pixelsPerSecond) const
{
const WaveTrack *const track = waveTrackCache.GetTrack().get();
const SpectrogramSettings &settings = track->GetSpectrogramSettings();
bool match =
mSpecCache &&
mSpecCache->len > 0 &&
mSpecCache->Matches
(mDirty, pixelsPerSecond, settings, mRate);
if (match &&
mSpecCache->start == t0 &&
mSpecCache->len >= numPixels) {
spectrogram = &mSpecCache->freq[0];
where = &mSpecCache->where[0];
return false; //hit cache completely
}
// Caching is not implemented for reassignment, unless for
// a complete hit, because of the complications of time reassignment
if (settings.algorithm == SpectrogramSettings::algReassignment)
match = false;
// Free the cache when it won't cause a major stutter.
// If the window size changed, we know there is nothing to be copied
// If we zoomed out, or resized, we can give up memory. But not too much -
// up to 2x extra is needed at the end of the clip to prevent stutter.
if (mSpecCache->freq.capacity() > 2.1 * mSpecCache->freq.size() ||
mSpecCache->windowSize*mSpecCache->zeroPaddingFactor <
settings.WindowSize()*settings.ZeroPaddingFactor())
{
match = false;
mSpecCache = std::make_unique<SpecCache>();
}
const double tstep = 1.0 / pixelsPerSecond;
const double samplesPerPixel = mRate * tstep;
int oldX0 = 0;
double correction = 0.0;
int copyBegin = 0, copyEnd = 0;
if (match) {
findCorrection(mSpecCache->where, mSpecCache->len, numPixels,
t0, mRate, samplesPerPixel,
oldX0, correction);
// Remember our first pixel maps to oldX0 in the old cache,
// possibly out of bounds.
// For what range of pixels can data be copied?
copyBegin = std::min((int)numPixels, std::max(0, -oldX0));
copyEnd = std::min((int)numPixels, std::max(0,
(int)mSpecCache->len - oldX0
));
}
// Resize the cache, keep the contents unchanged.
mSpecCache->Grow(numPixels, settings, pixelsPerSecond, t0);
auto nBins = settings.NBins();
// Optimization: if the old cache is good and overlaps
// with the current one, re-use as much of the cache as
// possible
if (copyEnd > copyBegin)
{
// memmove is required since dst/src overlap
memmove(&mSpecCache->freq[nBins * copyBegin],
&mSpecCache->freq[nBins * (copyBegin + oldX0)],
nBins * (copyEnd - copyBegin) * sizeof(float));
}
// Reassignment accumulates, so it needs a zeroed buffer
if (settings.algorithm == SpectrogramSettings::algReassignment)
{
// The cache could theoretically copy from the middle, resulting
// in two regions to update. This won't happen in zoom, since
// old cache doesn't match. It won't happen in resize, since the
// spectrum view is pinned to left side of window.
wxASSERT(
(copyBegin >= 0 && copyEnd == (int)numPixels) || // copied the end
(copyBegin == 0 && copyEnd <= (int)numPixels) // copied the beginning
);
int zeroBegin = copyBegin > 0 ? 0 : copyEnd-copyBegin;
int zeroEnd = copyBegin > 0 ? copyBegin : numPixels;
memset(&mSpecCache->freq[nBins*zeroBegin], 0, nBins*(zeroEnd-zeroBegin)*sizeof(float));
}
// purposely offset the display 1/2 sample to the left (as compared
// to waveform display) to properly center response of the FFT
fillWhere(mSpecCache->where, numPixels, 0.5, correction,
t0, mRate, samplesPerPixel);
mSpecCache->Populate
(settings, waveTrackCache, copyBegin, copyEnd, numPixels,
mSequence->GetNumSamples(),
mOffset, mRate, pixelsPerSecond);
mSpecCache->dirty = mDirty;
spectrogram = &mSpecCache->freq[0];
where = &mSpecCache->where[0];
return true;
}
std::pair<float, float> WaveClip::GetMinMax(
double t0, double t1, bool mayThrow) const
{
if (t0 > t1) {
if (mayThrow)
THROW_INCONSISTENCY_EXCEPTION;
return {
0.f, // harmless, but unused since Sequence::GetMinMax does not use these values
0.f // harmless, but unused since Sequence::GetMinMax does not use these values
};
}
if (t0 == t1)
return{ 0.f, 0.f };
sampleCount s0, s1;
TimeToSamplesClip(t0, &s0);
TimeToSamplesClip(t1, &s1);
return mSequence->GetMinMax(s0, s1-s0, mayThrow);
}
float WaveClip::GetRMS(double t0, double t1, bool mayThrow) const
{
if (t0 > t1) {
if (mayThrow)
THROW_INCONSISTENCY_EXCEPTION;
return 0.f;
}
if (t0 == t1)
return 0.f;
sampleCount s0, s1;
TimeToSamplesClip(t0, &s0);
TimeToSamplesClip(t1, &s1);
return mSequence->GetRMS(s0, s1-s0, mayThrow);
}
void WaveClip::ConvertToSampleFormat(sampleFormat format,
const std::function<void(size_t)> & progressReport)
{
// Note: it is not necessary to do this recursively to cutlines.
// They get converted as needed when they are expanded.
auto bChanged = mSequence->ConvertToSampleFormat(format, progressReport);
if (bChanged)
MarkChanged();
}
/*! @excsafety{No-fail} */
void WaveClip::UpdateEnvelopeTrackLen()
{
auto len = (mSequence->GetNumSamples().as_double()) / mRate;
if (len != mEnvelope->GetTrackLen())
mEnvelope->SetTrackLen(len, 1.0 / GetRate());
}
void WaveClip::TimeToSamplesClip(double t0, sampleCount *s0) const
{
if (t0 < mOffset)
*s0 = 0;
else if (t0 > mOffset + mSequence->GetNumSamples().as_double()/mRate)
*s0 = mSequence->GetNumSamples();
else
*s0 = sampleCount( floor(((t0 - mOffset) * mRate) + 0.5) );
}
/*! @excsafety{Strong} */
std::shared_ptr<SampleBlock> WaveClip::AppendNewBlock(
samplePtr buffer, sampleFormat format, size_t len)
{
return mSequence->AppendNewBlock( buffer, format, len );
}
/*! @excsafety{Strong} */
void WaveClip::AppendSharedBlock(const std::shared_ptr<SampleBlock> &pBlock)
{
mSequence->AppendSharedBlock( pBlock );
}
/*! @excsafety{Partial}
-- Some prefix (maybe none) of the buffer is appended,
and no content already flushed to disk is lost. */
bool WaveClip::Append(constSamplePtr buffer, sampleFormat format,
size_t len, unsigned int stride)
{
//wxLogDebug(wxT("Append: len=%lli"), (long long) len);
bool result = false;
auto maxBlockSize = mSequence->GetMaxBlockSize();
auto blockSize = mSequence->GetIdealAppendLen();
sampleFormat seqFormat = mSequence->GetSampleFormat();
if (!mAppendBuffer.ptr())
mAppendBuffer.Allocate(maxBlockSize, seqFormat);
auto cleanup = finally( [&] {
// use No-fail-guarantee
UpdateEnvelopeTrackLen();
MarkChanged();
} );
for(;;) {
if (mAppendBufferLen >= blockSize) {
// flush some previously appended contents
// use Strong-guarantee
mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize);
result = true;
// use No-fail-guarantee for rest of this "if"
memmove(mAppendBuffer.ptr(),
mAppendBuffer.ptr() + blockSize * SAMPLE_SIZE(seqFormat),
(mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat));
mAppendBufferLen -= blockSize;
blockSize = mSequence->GetIdealAppendLen();
}
if (len == 0)
break;
// use No-fail-guarantee for rest of this "for"
wxASSERT(mAppendBufferLen <= maxBlockSize);
auto toCopy = std::min(len, maxBlockSize - mAppendBufferLen);
CopySamples(buffer, format,
mAppendBuffer.ptr() + mAppendBufferLen * SAMPLE_SIZE(seqFormat),
seqFormat,
toCopy,
gHighQualityDither,
stride);
mAppendBufferLen += toCopy;
buffer += toCopy * SAMPLE_SIZE(format) * stride;
len -= toCopy;
}
return result;
}
/*! @excsafety{Mixed} */
/*! @excsafety{No-fail} -- The clip will be in a flushed state. */
/*! @excsafety{Partial}
-- Some initial portion (maybe none) of the append buffer of the
clip gets appended; no previously flushed contents are lost. */
void WaveClip::Flush()
{
//wxLogDebug(wxT("WaveClip::Flush"));
//wxLogDebug(wxT(" mAppendBufferLen=%lli"), (long long) mAppendBufferLen);
//wxLogDebug(wxT(" previous sample count %lli"), (long long) mSequence->GetNumSamples());
if (mAppendBufferLen > 0) {
auto cleanup = finally( [&] {
// Blow away the append buffer even in case of failure. May lose some
// data but don't leave the track in an un-flushed state.
// Use No-fail-guarantee of these steps.
mAppendBufferLen = 0;
UpdateEnvelopeTrackLen();
MarkChanged();
} );
mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(),
mAppendBufferLen);
}
//wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples());
}
bool WaveClip::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
if (!wxStrcmp(tag, wxT("waveclip")))
{
double dblValue;
long longValue;
while (*attrs)
{
const wxChar *attr = *attrs++;
const wxChar *value = *attrs++;
if (!value)
break;
const wxString strValue = value;
if (!wxStrcmp(attr, wxT("offset")))
{
if (!XMLValueChecker::IsGoodString(strValue) ||
!Internat::CompatibleToDouble(strValue, &dblValue))
return false;
SetOffset(dblValue);
}
if (!wxStrcmp(attr, wxT("colorindex")))
{
if (!XMLValueChecker::IsGoodString(strValue) ||
!strValue.ToLong( &longValue))
return false;
SetColourIndex(longValue);
}
}
return true;
}
return false;
}
void WaveClip::HandleXMLEndTag(const wxChar *tag)
{
if (!wxStrcmp(tag, wxT("waveclip")))
UpdateEnvelopeTrackLen();
}
XMLTagHandler *WaveClip::HandleXMLChild(const wxChar *tag)
{
if (!wxStrcmp(tag, wxT("sequence")))
return mSequence.get();
else if (!wxStrcmp(tag, wxT("envelope")))
return mEnvelope.get();
else if (!wxStrcmp(tag, wxT("waveclip")))
{
// Nested wave clips are cut lines
mCutLines.push_back(
std::make_unique<WaveClip>(mSequence->GetFactory(),
mSequence->GetSampleFormat(), mRate, 0 /*colourindex*/));
return mCutLines.back().get();
}
else
return NULL;
}
void WaveClip::WriteXML(XMLWriter &xmlFile) const
// may throw
{
xmlFile.StartTag(wxT("waveclip"));
xmlFile.WriteAttr(wxT("offset"), mOffset, 8);
xmlFile.WriteAttr(wxT("colorindex"), mColourIndex );
mSequence->WriteXML(xmlFile);
mEnvelope->WriteXML(xmlFile);
for (const auto &clip: mCutLines)
clip->WriteXML(xmlFile);
xmlFile.EndTag(wxT("waveclip"));
}
/*! @excsafety{Strong} */
void WaveClip::Paste(double t0, const WaveClip* other)
{
const bool clipNeedsResampling = other->mRate != mRate;
const bool clipNeedsNewFormat =
other->mSequence->GetSampleFormat() != mSequence->GetSampleFormat();
std::unique_ptr<WaveClip> newClip;
const WaveClip* pastedClip;
if (clipNeedsResampling || clipNeedsNewFormat)
{
newClip =
std::make_unique<WaveClip>(*other, mSequence->GetFactory(), true);
if (clipNeedsResampling)
// The other clip's rate is different from ours, so resample
newClip->Resample(mRate);
if (clipNeedsNewFormat)
// Force sample formats to match.
newClip->ConvertToSampleFormat(mSequence->GetSampleFormat());
pastedClip = newClip.get();
}
else
{
// No resampling or format change needed, just use original clip without making a copy
pastedClip = other;
}
// Paste cut lines contained in pasted clip
WaveClipHolders newCutlines;
for (const auto &cutline: pastedClip->mCutLines)
{
newCutlines.push_back(
std::make_unique<WaveClip>
( *cutline, mSequence->GetFactory(),
// Recursively copy cutlines of cutlines. They don't need
// their offsets adjusted.
true));
newCutlines.back()->Offset(t0 - mOffset);
}
sampleCount s0;
TimeToSamplesClip(t0, &s0);
// Assume Strong-guarantee from Sequence::Paste
mSequence->Paste(s0, pastedClip->mSequence.get());
// Assume No-fail-guarantee in the remaining
MarkChanged();
auto sampleTime = 1.0 / GetRate();
mEnvelope->PasteEnvelope
(s0.as_double()/mRate + mOffset, pastedClip->mEnvelope.get(), sampleTime);
OffsetCutLines(t0, pastedClip->GetEndTime() - pastedClip->GetStartTime());
for (auto &holder : newCutlines)
mCutLines.push_back(std::move(holder));
}
/*! @excsafety{Strong} */
void WaveClip::InsertSilence( double t, double len, double *pEnvelopeValue )
{
sampleCount s0;
TimeToSamplesClip(t, &s0);
auto slen = (sampleCount)floor(len * mRate + 0.5);
// use Strong-guarantee
GetSequence()->InsertSilence(s0, slen);
// use No-fail-guarantee
OffsetCutLines(t, len);
const auto sampleTime = 1.0 / GetRate();
auto pEnvelope = GetEnvelope();
if ( pEnvelopeValue ) {
// Preserve limit value at the end
auto oldLen = pEnvelope->GetTrackLen();
auto newLen = oldLen + len;
pEnvelope->Cap( sampleTime );
// Ramp across the silence to the given value
pEnvelope->SetTrackLen( newLen, sampleTime );
pEnvelope->InsertOrReplace
( pEnvelope->GetOffset() + newLen, *pEnvelopeValue );
}
else
pEnvelope->InsertSpace( t, len );
MarkChanged();
}
/*! @excsafety{Strong} */
void WaveClip::AppendSilence( double len, double envelopeValue )
{
auto t = GetEndTime();
InsertSilence( t, len, &envelopeValue );
}
/*! @excsafety{Strong} */
void WaveClip::Clear(double t0, double t1)
{
sampleCount s0, s1;
TimeToSamplesClip(t0, &s0);
TimeToSamplesClip(t1, &s1);
// use Strong-guarantee
GetSequence()->Delete(s0, s1-s0);
// use No-fail-guarantee in the remaining
// msmeyer
//
// Delete all cutlines that are within the given area, if any.
//
// Note that when cutlines are active, two functions are used:
// Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
// whenever the user directly calls a command that removes some audio, e.g.
// "Cut" or "Clear" from the menu. This command takes care about recursive
// preserving of cutlines within clips. Clear() is called when internal
// operations want to remove audio. In the latter case, it is the right
// thing to just remove all cutlines within the area.
//
double clip_t0 = t0;
double clip_t1 = t1;
if (clip_t0 < GetStartTime())
clip_t0 = GetStartTime();
if (clip_t1 > GetEndTime())
clip_t1 = GetEndTime();
// May DELETE as we iterate, so don't use range-for
for (auto it = mCutLines.begin(); it != mCutLines.end();)
{
WaveClip* clip = it->get();
double cutlinePosition = mOffset + clip->GetOffset();
if (cutlinePosition >= t0 && cutlinePosition <= t1)
{
// This cutline is within the area, DELETE it
it = mCutLines.erase(it);
}
else
{
if (cutlinePosition >= t1)
{
clip->Offset(clip_t0 - clip_t1);
}
++it;
}
}
// Collapse envelope
auto sampleTime = 1.0 / GetRate();
GetEnvelope()->CollapseRegion( t0, t1, sampleTime );
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
MarkChanged();
}
/*! @excsafety{Weak}
-- This WaveClip remains destructible in case of AudacityException.
But some cutlines may be deleted */
void WaveClip::ClearAndAddCutLine(double t0, double t1)
{
if (t0 > GetEndTime() || t1 < GetStartTime())
return; // time out of bounds
const double clip_t0 = std::max( t0, GetStartTime() );
const double clip_t1 = std::min( t1, GetEndTime() );
auto newClip = std::make_unique< WaveClip >
(*this, mSequence->GetFactory(), true, clip_t0, clip_t1);
newClip->SetOffset( clip_t0 - mOffset );
// Remove cutlines from this clip that were in the selection, shift
// left those that were after the selection
// May DELETE as we iterate, so don't use range-for
for (auto it = mCutLines.begin(); it != mCutLines.end();)
{
WaveClip* clip = it->get();
double cutlinePosition = mOffset + clip->GetOffset();
if (cutlinePosition >= t0 && cutlinePosition <= t1)
it = mCutLines.erase(it);
else
{
if (cutlinePosition >= t1)
{
clip->Offset(clip_t0 - clip_t1);
}
++it;
}
}
// Clear actual audio data
sampleCount s0, s1;
TimeToSamplesClip(t0, &s0);
TimeToSamplesClip(t1, &s1);
// use Weak-guarantee
GetSequence()->Delete(s0, s1-s0);
// Collapse envelope
auto sampleTime = 1.0 / GetRate();
GetEnvelope()->CollapseRegion( t0, t1, sampleTime );
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
MarkChanged();
mCutLines.push_back(std::move(newClip));
}
bool WaveClip::FindCutLine(double cutLinePosition,
double* cutlineStart /* = NULL */,
double* cutlineEnd /* = NULL */) const
{
for (const auto &cutline: mCutLines)
{
if (fabs(mOffset + cutline->GetOffset() - cutLinePosition) < 0.0001)
{
if (cutlineStart)
*cutlineStart = mOffset+cutline->GetStartTime();
if (cutlineEnd)
*cutlineEnd = mOffset+cutline->GetEndTime();
return true;
}
}
return false;
}
/*! @excsafety{Strong} */
void WaveClip::ExpandCutLine(double cutLinePosition)
{
auto end = mCutLines.end();
auto it = std::find_if( mCutLines.begin(), end,
[&](const WaveClipHolder &cutline) {
return fabs(mOffset + cutline->GetOffset() - cutLinePosition) < 0.0001;
} );
if ( it != end ) {
auto cutline = it->get();
// assume Strong-guarantee from Paste
// Envelope::Paste takes offset into account, WaveClip::Paste doesn't!
// Do this to get the right result:
cutline->mEnvelope->SetOffset(0);
Paste(mOffset+cutline->GetOffset(), cutline);
// Now erase the cutline,
// but be careful to find it again, because Paste above may
// have modified the array of cutlines (if our cutline contained
// another cutline!), invalidating the iterator we had.
end = mCutLines.end();
it = std::find_if(mCutLines.begin(), end,
[=](const WaveClipHolder &p) { return p.get() == cutline; });
if (it != end)
mCutLines.erase(it); // deletes cutline!
else {
wxASSERT(false);
}
}
}
bool WaveClip::RemoveCutLine(double cutLinePosition)
{
for (auto it = mCutLines.begin(); it != mCutLines.end(); ++it)
{
const auto &cutline = *it;
if (fabs(mOffset + cutline->GetOffset() - cutLinePosition) < 0.0001)
{
mCutLines.erase(it); // deletes cutline!
return true;
}
}
return false;
}
/*! @excsafety{No-fail} */
void WaveClip::OffsetCutLines(double t0, double len)
{
for (const auto &cutLine : mCutLines)
{
if (mOffset + cutLine->GetOffset() >= t0)
cutLine->Offset(len);
}
}
void WaveClip::CloseLock()
{
GetSequence()->CloseLock();
for (const auto &cutline: mCutLines)
cutline->CloseLock();
}
void WaveClip::SetRate(int rate)
{
mRate = rate;
auto newLength = mSequence->GetNumSamples().as_double() / mRate;
mEnvelope->RescaleTimes( newLength );
MarkChanged();
}
/*! @excsafety{Strong} */
void WaveClip::Resample(int rate, ProgressDialog *progress)
{
// Note: it is not necessary to do this recursively to cutlines.
// They get resampled as needed when they are expanded.
if (rate == mRate)
return; // Nothing to do
double factor = (double)rate / (double)mRate;
::Resample resample(true, factor, factor); // constant rate resampling
const size_t bufsize = 65536;
Floats inBuffer{ bufsize };
Floats outBuffer{ bufsize };
sampleCount pos = 0;
bool error = false;
int outGenerated = 0;
auto numSamples = mSequence->GetNumSamples();
auto newSequence =
std::make_unique<Sequence>(mSequence->GetFactory(), mSequence->GetSampleFormat());
/**
* We want to keep going as long as we have something to feed the resampler
* with OR as long as the resampler spews out samples (which could continue
* for a few iterations after we stop feeding it)
*/
while (pos < numSamples || outGenerated > 0)
{
const auto inLen = limitSampleBufferSize( bufsize, numSamples - pos );
bool isLast = ((pos + inLen) == numSamples);
if (!mSequence->Get((samplePtr)inBuffer.get(), floatSample, pos, inLen, true))
{
error = true;
break;
}
const auto results = resample.Process(factor, inBuffer.get(), inLen, isLast,
outBuffer.get(), bufsize);
outGenerated = results.second;
pos += results.first;
if (outGenerated < 0)
{
error = true;
break;
}
newSequence->Append((samplePtr)outBuffer.get(), floatSample,
outGenerated);
if (progress)
{
auto updateResult = progress->Update(
pos.as_long_long(),
numSamples.as_long_long()
);
error = (updateResult != ProgressResult::Success);
if (error)
throw UserException{};
}
}
if (error)
throw SimpleMessageBoxException{
ExceptionType::Internal,
XO("Resampling failed."),
XO("Warning"),
"Error:_Resampling"
};
else
{
// Use No-fail-guarantee in these steps
// Invalidate wave display cache
mWaveCache = std::make_unique<WaveCache>();
// Invalidate the spectrum display cache
mSpecCache = std::make_unique<SpecCache>();
mSequence = std::move(newSequence);
mRate = rate;
}
}
// Used by commands which interact with clips using the keyboard.
// When two clips are immediately next to each other, the GetEndTime()
// of the first clip and the GetStartTime() of the second clip may not
// be exactly equal due to rounding errors.
bool WaveClip::SharesBoundaryWithNextClip(const WaveClip* next) const
{
double endThis = GetRate() * GetOffset() + GetNumSamples().as_double();
double startNext = next->GetRate() * next->GetOffset();
// given that a double has about 15 significant digits, using a criterion
// of half a sample should be safe in all normal usage.
return fabs(startNext - endThis) < 0.5;
}