1612 lines
47 KiB
C++
1612 lines
47 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).
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class SpecCache
|
|
\brief Cache used with WaveClip to cache spectrum information (for
|
|
drawing). Cache's the Spectrogram frequency samples.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include <math.h>
|
|
#include <vector>
|
|
#include <wx/log.h>
|
|
|
|
#include "Spectrum.h"
|
|
#include "Prefs.h"
|
|
#include "WaveClip.h"
|
|
#include "Envelope.h"
|
|
#include "Resample.h"
|
|
#include "Project.h"
|
|
|
|
#include <wx/listimpl.cpp>
|
|
WX_DEFINE_LIST(WaveClipList);
|
|
|
|
class WaveCache {
|
|
public:
|
|
WaveCache(int cacheLen)
|
|
{
|
|
dirty = -1;
|
|
start = -1.0;
|
|
pps = 0.0;
|
|
len = cacheLen;
|
|
min = new float[len];
|
|
max = new float[len];
|
|
rms = new float[len];
|
|
bl = new int[len];
|
|
where = new sampleCount[len+1];
|
|
numODPixels=0;
|
|
}
|
|
|
|
~WaveCache()
|
|
{
|
|
delete[] min;
|
|
delete[] max;
|
|
delete[] rms;
|
|
delete[] bl;
|
|
delete[] where;
|
|
|
|
ClearInvalidRegions();
|
|
}
|
|
|
|
int dirty;
|
|
sampleCount len;
|
|
double start;
|
|
double pps;
|
|
int rate;
|
|
sampleCount *where;
|
|
float *min;
|
|
float *max;
|
|
float *rms;
|
|
int *bl;
|
|
int numODPixels;
|
|
|
|
class InvalidRegion
|
|
{
|
|
public:
|
|
InvalidRegion(int s, int e):start(s),end(e){}
|
|
//start and end pixel count. (not samples)
|
|
int start;
|
|
int end;
|
|
};
|
|
|
|
|
|
//Thread safe call to add a new region to invalidate. If it overlaps with other regions, it unions the them.
|
|
void AddInvalidRegion(sampleCount sampleStart, sampleCount sampleEnd)
|
|
{
|
|
//use pps to figure out where we are. (pixels per second)
|
|
if(pps ==0)
|
|
return;
|
|
double samplesPerPixel = rate/pps;
|
|
//rate is SR, start is first time of the waveform (in second) on cache
|
|
long invalStart = (sampleStart - start*rate)/samplesPerPixel ;
|
|
|
|
long invalEnd = (sampleEnd - start*rate)/samplesPerPixel +1; //we should cover the end..
|
|
|
|
//if they are both off the cache boundary in the same direction, the cache is missed,
|
|
//so we are safe, and don't need to track this one.
|
|
if((invalStart<0 && invalEnd <0) || (invalStart>=len && invalEnd >= len))
|
|
return;
|
|
|
|
//in all other cases, we need to clip the boundries so they make sense with the cache.
|
|
//for some reason, the cache is set up to access up to array[len], not array[len-1]
|
|
if(invalStart <0)
|
|
invalStart =0;
|
|
else if(invalStart > len)
|
|
invalStart = len;
|
|
|
|
if(invalEnd <0)
|
|
invalEnd =0;
|
|
else if(invalEnd > len)
|
|
invalEnd = len;
|
|
|
|
|
|
mRegionsMutex.Lock();
|
|
|
|
//look thru the region array for a place to insert. We could make this more spiffy than a linear search
|
|
//but right now it is not needed since there will usually only be one region (which grows) for OD loading.
|
|
bool added=false;
|
|
if(mRegions.size())
|
|
{
|
|
for(size_t i=0;i<mRegions.size();i++)
|
|
{
|
|
//if the regions intersect OR are pixel adjacent
|
|
if(mRegions[i]->start <= invalEnd+1
|
|
&& mRegions[i]->end >= invalStart-1)
|
|
{
|
|
//take the union region
|
|
if(mRegions[i]->start > invalStart)
|
|
mRegions[i]->start = invalStart;
|
|
if(mRegions[i]->end < invalEnd)
|
|
mRegions[i]->end = invalEnd;
|
|
added=true;
|
|
break;
|
|
}
|
|
|
|
//this bit doesn't make sense because it assumes we add in order - now we go backwards after the initial OD finishes
|
|
// //this array is sorted by start/end points and has no overlaps. If we've passed all possible intersections, insert. The array will remain sorted.
|
|
// if(mRegions[i]->end < invalStart)
|
|
// {
|
|
// InvalidRegion* newRegion = new InvalidRegion(invalStart,invalEnd);
|
|
// mRegions.insert(mRegions.begin()+i,newRegion);
|
|
// break;
|
|
// }
|
|
}
|
|
}
|
|
|
|
if(!added)
|
|
{
|
|
InvalidRegion* newRegion = new InvalidRegion(invalStart,invalEnd);
|
|
mRegions.insert(mRegions.begin(),newRegion);
|
|
}
|
|
|
|
|
|
//now we must go and patch up all the regions that overlap. Overlapping regions will be adjacent.
|
|
for(size_t i=1;i<mRegions.size();i++)
|
|
{
|
|
//if the regions intersect OR are pixel adjacent
|
|
if(mRegions[i]->start <= mRegions[i-1]->end+1
|
|
&& mRegions[i]->end >= mRegions[i-1]->start-1)
|
|
{
|
|
//take the union region
|
|
if(mRegions[i]->start > mRegions[i-1]->start)
|
|
mRegions[i]->start = mRegions[i-1]->start;
|
|
if(mRegions[i]->end < mRegions[i-1]->end)
|
|
mRegions[i]->end = mRegions[i-1]->end;
|
|
|
|
//now we must delete the previous region
|
|
delete mRegions[i-1];
|
|
mRegions.erase(mRegions.begin()+i-1);
|
|
//musn't forget to reset cursor
|
|
i--;
|
|
}
|
|
|
|
//if we are past the end of the region we added, we are past the area of regions that might be oversecting.
|
|
if(mRegions[i]->start > invalEnd)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
mRegionsMutex.Unlock();
|
|
}
|
|
|
|
//lock before calling these in a section. unlock after finished.
|
|
int GetNumInvalidRegions(){return mRegions.size();}
|
|
int GetInvalidRegionStart(int i){return mRegions[i]->start;}
|
|
int GetInvalidRegionEnd(int i){return mRegions[i]->end;}
|
|
|
|
void LockInvalidRegions(){mRegionsMutex.Lock();}
|
|
void UnlockInvalidRegions(){mRegionsMutex.Unlock();}
|
|
|
|
void ClearInvalidRegions()
|
|
{
|
|
for(size_t i =0;i<mRegions.size();i++)
|
|
{
|
|
delete mRegions[i];
|
|
}
|
|
mRegions.clear();
|
|
}
|
|
|
|
|
|
protected:
|
|
std::vector<InvalidRegion*> mRegions;
|
|
ODLock mRegionsMutex;
|
|
|
|
};
|
|
|
|
class SpecCache {
|
|
public:
|
|
SpecCache(int cacheLen, int half, bool autocorrelation)
|
|
{
|
|
minFreqOld = -1;
|
|
maxFreqOld = -1;
|
|
gainOld = -1;
|
|
rangeOld = -1;
|
|
windowTypeOld = -1;
|
|
windowSizeOld = -1;
|
|
frequencyGainOld = false;
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
fftSkipPointsOld = -1;
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
dirty = -1;
|
|
start = -1.0;
|
|
pps = 0.0;
|
|
len = cacheLen;
|
|
ac = autocorrelation;
|
|
freq = new float[len*half];
|
|
where = new sampleCount[len+1];
|
|
}
|
|
|
|
~SpecCache()
|
|
{
|
|
delete[] freq;
|
|
delete[] where;
|
|
}
|
|
|
|
int minFreqOld;
|
|
int maxFreqOld;
|
|
int gainOld;
|
|
int rangeOld;
|
|
int windowTypeOld;
|
|
int windowSizeOld;
|
|
int frequencyGainOld;
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
int fftSkipPointsOld;
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
int dirty;
|
|
bool ac;
|
|
sampleCount len;
|
|
double start;
|
|
double pps;
|
|
sampleCount *where;
|
|
float *freq;
|
|
};
|
|
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
#include "FFT.h"
|
|
void ComputeSpectrumUsingRealFFTf(float *buffer, HFFT hFFT, float *window, int len, float *out)
|
|
{
|
|
int 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*log10(power);
|
|
for(i=1;i<hFFT->Points;i++) {
|
|
power = (buffer[hFFT->BitReversed[i] ]*buffer[hFFT->BitReversed[i] ])
|
|
+ (buffer[hFFT->BitReversed[i]+1]*buffer[hFFT->BitReversed[i]+1]);
|
|
if(power <= 0)
|
|
out[i] = -160.0;
|
|
else
|
|
out[i] = 10.0*log10f(power);
|
|
}
|
|
}
|
|
#endif // EXPERIMENTAL_USE_REALFFTF
|
|
|
|
WaveClip::WaveClip(DirManager *projDirManager, sampleFormat format, int rate)
|
|
{
|
|
mOffset = 0;
|
|
mRate = rate;
|
|
mSequence = new Sequence(projDirManager, format);
|
|
mEnvelope = new Envelope();
|
|
mWaveCache = new WaveCache(1);
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
mWindowType = -1;
|
|
mWindowSize = -1;
|
|
hFFT = NULL;
|
|
mWindow = NULL;
|
|
#endif
|
|
mSpecCache = new SpecCache(1, 1, false);
|
|
mSpecPxCache = new SpecPxCache(1);
|
|
mAppendBuffer = NULL;
|
|
mAppendBufferLen = 0;
|
|
mDirty = 0;
|
|
mIsPlaceholder = false;
|
|
}
|
|
|
|
WaveClip::WaveClip(WaveClip& orig, DirManager *projDirManager)
|
|
{
|
|
// essentially a copy constructor - but you must pass in the
|
|
// current project's DirManager, because we might be copying
|
|
// from one project to another
|
|
|
|
mOffset = orig.mOffset;
|
|
mRate = orig.mRate;
|
|
mSequence = new Sequence(*orig.mSequence, projDirManager);
|
|
mEnvelope = new Envelope();
|
|
mEnvelope->Paste(0.0, orig.mEnvelope);
|
|
mEnvelope->SetOffset(orig.GetOffset());
|
|
mEnvelope->SetTrackLen(((double)orig.mSequence->GetNumSamples()) / orig.mRate);
|
|
mWaveCache = new WaveCache(1);
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
mWindowType = -1;
|
|
mWindowSize = -1;
|
|
hFFT = NULL;
|
|
mWindow = NULL;
|
|
#endif
|
|
mSpecCache = new SpecCache(1, 1, false);
|
|
mSpecPxCache = new SpecPxCache(1);
|
|
|
|
for (WaveClipList::compatibility_iterator it=orig.mCutLines.GetFirst(); it; it=it->GetNext())
|
|
mCutLines.Append(new WaveClip(*it->GetData(), projDirManager));
|
|
|
|
mAppendBuffer = NULL;
|
|
mAppendBufferLen = 0;
|
|
mDirty = 0;
|
|
mIsPlaceholder = orig.GetIsPlaceholder();
|
|
}
|
|
|
|
WaveClip::~WaveClip()
|
|
{
|
|
delete mSequence;
|
|
delete mEnvelope;
|
|
delete mWaveCache;
|
|
delete mSpecCache;
|
|
delete mSpecPxCache;
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
if(hFFT != NULL)
|
|
EndFFT(hFFT);
|
|
if(mWindow != NULL)
|
|
delete[] mWindow;
|
|
#endif
|
|
|
|
if (mAppendBuffer)
|
|
DeleteSamples(mAppendBuffer);
|
|
|
|
mCutLines.DeleteContents(true);
|
|
mCutLines.Clear();
|
|
}
|
|
|
|
void WaveClip::SetOffset(double offset)
|
|
{
|
|
mOffset = offset;
|
|
mEnvelope->SetOffset(mOffset);
|
|
}
|
|
|
|
bool WaveClip::GetSamples(samplePtr buffer, sampleFormat format,
|
|
sampleCount start, sampleCount len) const
|
|
{
|
|
return mSequence->Get(buffer, format, start, len);
|
|
}
|
|
|
|
bool WaveClip::SetSamples(samplePtr buffer, sampleFormat format,
|
|
sampleCount start, sampleCount len)
|
|
{
|
|
bool bResult = mSequence->Set(buffer, format, start, len);
|
|
MarkChanged();
|
|
return bResult;
|
|
}
|
|
|
|
double WaveClip::GetStartTime() const
|
|
{
|
|
// JS: mOffset is the minimum value and it is returned; no clipping to 0
|
|
return mOffset;
|
|
}
|
|
|
|
double WaveClip::GetEndTime() const
|
|
{
|
|
sampleCount numSamples = mSequence->GetNumSamples();
|
|
|
|
double maxLen = mOffset + double(numSamples+mAppendBufferLen)/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();
|
|
}
|
|
|
|
bool WaveClip::WithinClip(double t) const
|
|
{
|
|
sampleCount ts = (sampleCount)floor(t * mRate + 0.5);
|
|
return ts > GetStartSample() && ts < GetEndSample() + mAppendBufferLen;
|
|
}
|
|
|
|
bool WaveClip::BeforeClip(double t) const
|
|
{
|
|
sampleCount ts = (sampleCount)floor(t * mRate + 0.5);
|
|
return ts <= GetStartSample();
|
|
}
|
|
|
|
bool WaveClip::AfterClip(double t) const
|
|
{
|
|
sampleCount ts = (sampleCount)floor(t * mRate + 0.5);
|
|
return ts >= GetEndSample() + mAppendBufferLen;
|
|
}
|
|
|
|
///Delete the wave cache - force redraw. Thread-safe
|
|
void WaveClip::DeleteWaveCache()
|
|
{
|
|
mWaveCacheMutex.Lock();
|
|
if(mWaveCache!=NULL)
|
|
delete mWaveCache;
|
|
mWaveCache = new WaveCache(1);
|
|
mWaveCacheMutex.Unlock();
|
|
}
|
|
|
|
///Adds an invalid region to the wavecache so it redraws that portion only.
|
|
void WaveClip::AddInvalidRegion(long startSample, long endSample)
|
|
{
|
|
mWaveCacheMutex.Lock();
|
|
if(mWaveCache!=NULL)
|
|
mWaveCache->AddInvalidRegion(startSample,endSample);
|
|
mWaveCacheMutex.Unlock();
|
|
}
|
|
|
|
//
|
|
// Getting high-level data from the track for screen display and
|
|
// clipping calculations
|
|
//
|
|
|
|
bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
|
|
sampleCount *where,
|
|
int numPixels, double t0,
|
|
double pixelsPerSecond, bool &isLoadingOD)
|
|
{
|
|
mWaveCacheMutex.Lock();
|
|
|
|
|
|
if (mWaveCache &&
|
|
mWaveCache->dirty == mDirty &&
|
|
mWaveCache->start == t0 &&
|
|
mWaveCache->len >= numPixels &&
|
|
mWaveCache->pps == pixelsPerSecond) {
|
|
|
|
//check for invalid regions, and make the bottom if an else if.
|
|
//invalid regions are kept in a sorted array.
|
|
for(int i=0;i<mWaveCache->GetNumInvalidRegions();i++)
|
|
{
|
|
int invStart;
|
|
invStart = mWaveCache->GetInvalidRegionStart(i);
|
|
int invEnd;
|
|
invEnd = mWaveCache->GetInvalidRegionEnd(i);
|
|
|
|
int regionODPixels;
|
|
regionODPixels =0;
|
|
int regionODPixelsAfter;
|
|
regionODPixelsAfter =0;
|
|
//before check number of ODPixels
|
|
for(int j=invStart;j<invEnd;j++)
|
|
{
|
|
if(mWaveCache->bl[j]<0)
|
|
regionODPixels++;
|
|
}
|
|
mSequence->GetWaveDisplay(&mWaveCache->min[invStart],
|
|
&mWaveCache->max[invStart],
|
|
&mWaveCache->rms[invStart],
|
|
&mWaveCache->bl[invStart],
|
|
invEnd-invStart,
|
|
&mWaveCache->where[invStart],
|
|
mRate / pixelsPerSecond);
|
|
//after check number of ODPixels
|
|
for(int j=invStart;j<invEnd;j++)
|
|
{
|
|
if(mWaveCache->bl[j]<0)
|
|
regionODPixelsAfter++;
|
|
}
|
|
//decrement the number of od pixels.
|
|
mWaveCache->numODPixels -= (regionODPixels - regionODPixelsAfter);
|
|
}
|
|
mWaveCache->ClearInvalidRegions();
|
|
|
|
|
|
memcpy(min, mWaveCache->min, numPixels*sizeof(float));
|
|
memcpy(max, mWaveCache->max, numPixels*sizeof(float));
|
|
memcpy(rms, mWaveCache->rms, numPixels*sizeof(float));
|
|
memcpy(bl, mWaveCache->bl, numPixels*sizeof(int));
|
|
memcpy(where, mWaveCache->where, (numPixels+1)*sizeof(sampleCount));
|
|
isLoadingOD = mWaveCache->numODPixels>0;
|
|
mWaveCacheMutex.Unlock();
|
|
return true;
|
|
}
|
|
|
|
WaveCache *oldCache = mWaveCache;
|
|
|
|
mWaveCache = new WaveCache(numPixels);
|
|
mWaveCache->pps = pixelsPerSecond;
|
|
mWaveCache->rate = mRate;
|
|
mWaveCache->start = t0;
|
|
double tstep = 1.0 / pixelsPerSecond;
|
|
|
|
sampleCount x;
|
|
|
|
for (x = 0; x < mWaveCache->len + 1; x++) {
|
|
mWaveCache->where[x] =
|
|
(sampleCount) floor(t0 * mRate +
|
|
((double) x) * mRate * tstep + 0.5);
|
|
}
|
|
|
|
//mchinen: I think s0 - s1 represents the range of samples that we will need to look up. likewise p0-p1 the number of pixels.
|
|
sampleCount s0 = mWaveCache->where[0];
|
|
sampleCount s1 = mWaveCache->where[mWaveCache->len];
|
|
int p0 = 0;
|
|
int p1 = mWaveCache->len;
|
|
|
|
// Optimization: if the old cache is good and overlaps
|
|
// with the current one, re-use as much of the cache as
|
|
// possible
|
|
if (oldCache->dirty == mDirty &&
|
|
oldCache->pps == pixelsPerSecond &&
|
|
oldCache->where[0] < mWaveCache->where[mWaveCache->len] &&
|
|
oldCache->where[oldCache->len] > mWaveCache->where[0]) {
|
|
|
|
//now we are assuming the entire range is covered by the old cache and reducing s1/s0 as we find out otherwise.
|
|
s0 = mWaveCache->where[mWaveCache->len]; //mchinen:s0 is the min sample covered up to by the wave cache. will shrink if old doen't overlap
|
|
s1 = mWaveCache->where[0]; //mchinen - same, but the maximum sample covered.
|
|
p0 = mWaveCache->len;
|
|
p1 = 0;
|
|
|
|
//check for invalid regions, and make the bottom if an else if.
|
|
//invalid regions are keep in a sorted array.
|
|
//TODO:integrate into below for loop so that we only load inval regions if
|
|
//necessary. (usually is the case, so no rush.)
|
|
//also, we should be updating the NEW cache, but here we are patching the old one up.
|
|
for(int i=0;i<oldCache->GetNumInvalidRegions();i++)
|
|
{
|
|
int invStart;
|
|
invStart = oldCache->GetInvalidRegionStart(i);
|
|
int invEnd;
|
|
invEnd = oldCache->GetInvalidRegionEnd(i);
|
|
mSequence->GetWaveDisplay(&oldCache->min[invStart],
|
|
&oldCache->max[invStart],
|
|
&oldCache->rms[invStart],
|
|
&oldCache->bl[invStart],
|
|
invEnd-invStart,
|
|
&oldCache->where[invStart],
|
|
mRate / pixelsPerSecond);
|
|
}
|
|
oldCache->ClearInvalidRegions();
|
|
|
|
for (x = 0; x < mWaveCache->len; x++)
|
|
{
|
|
|
|
|
|
|
|
//below is regular cache access.
|
|
if (mWaveCache->where[x] >= oldCache->where[0] &&
|
|
mWaveCache->where[x] <= oldCache->where[oldCache->len - 1]) {
|
|
|
|
//if we hit an invalid region, load it up.
|
|
|
|
int ox =
|
|
int ((double (oldCache->len) *
|
|
(mWaveCache->where[x] -
|
|
oldCache->where[0])) /(oldCache->where[oldCache->len] -
|
|
oldCache->where[0]) + 0.5);
|
|
|
|
mWaveCache->min[x] = oldCache->min[ox];
|
|
mWaveCache->max[x] = oldCache->max[ox];
|
|
mWaveCache->rms[x] = oldCache->rms[ox];
|
|
mWaveCache->bl[x] = oldCache->bl[ox];
|
|
} else {
|
|
if (mWaveCache->where[x] < s0) {
|
|
s0 = mWaveCache->where[x];
|
|
p0 = x;
|
|
}
|
|
if (mWaveCache->where[x + 1] > s1) {
|
|
s1 = mWaveCache->where[x + 1];
|
|
p1 = x + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p1 > p0) {
|
|
|
|
/* handle values in the append buffer */
|
|
|
|
int numSamples = mSequence->GetNumSamples();
|
|
int a;
|
|
|
|
for(a=p0; a<p1; a++)
|
|
if (mWaveCache->where[a+1] > numSamples)
|
|
break;
|
|
|
|
//compute the values that are outside the overlap from scratch.
|
|
if (a < p1) {
|
|
int i;
|
|
|
|
sampleFormat seqFormat = mSequence->GetSampleFormat();
|
|
bool didUpdate = false;
|
|
for(i=a; i<p1; i++) {
|
|
sampleCount left;
|
|
left = mWaveCache->where[i] - numSamples;
|
|
sampleCount right;
|
|
right = mWaveCache->where[i+1] - numSamples;
|
|
|
|
//wxCriticalSectionLocker locker(mAppendCriticalSection);
|
|
|
|
if (left < 0)
|
|
left = 0;
|
|
if (right > mAppendBufferLen)
|
|
right = mAppendBufferLen;
|
|
|
|
if (right > left) {
|
|
float *b;
|
|
sampleCount len = right-left;
|
|
sampleCount j;
|
|
|
|
if (seqFormat == floatSample)
|
|
b = &((float *)mAppendBuffer)[left];
|
|
else {
|
|
b = new float[len];
|
|
CopySamples(mAppendBuffer + left*SAMPLE_SIZE(seqFormat),
|
|
seqFormat,
|
|
(samplePtr)b, floatSample, len);
|
|
}
|
|
|
|
float max = b[0];
|
|
float min = b[0];
|
|
float sumsq = b[0] * b[0];
|
|
|
|
for(j=1; j<len; j++) {
|
|
if (b[j] > max)
|
|
max = b[j];
|
|
if (b[j] < min)
|
|
min = b[j];
|
|
sumsq += b[j]*b[j];
|
|
}
|
|
|
|
mWaveCache->min[i] = min;
|
|
mWaveCache->max[i] = max;
|
|
mWaveCache->rms[i] = (float)sqrt(sumsq / len);
|
|
mWaveCache->bl[i] = 1; //for now just fake it.
|
|
|
|
if (seqFormat != floatSample)
|
|
delete[] b;
|
|
|
|
didUpdate=true;
|
|
}
|
|
}
|
|
|
|
// So that the sequence doesn't try to write any
|
|
// of these values
|
|
//mchinen: but only do this if we've updated pixels in the cache.
|
|
if(didUpdate)
|
|
p1 = a;
|
|
}
|
|
|
|
if (p1 > p0) {
|
|
if (!mSequence->GetWaveDisplay(&mWaveCache->min[p0],
|
|
&mWaveCache->max[p0],
|
|
&mWaveCache->rms[p0],
|
|
&mWaveCache->bl[p0],
|
|
p1-p0,
|
|
&mWaveCache->where[p0],
|
|
mRate / pixelsPerSecond))
|
|
{
|
|
isLoadingOD=false;
|
|
mWaveCacheMutex.Unlock();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
mWaveCache->dirty = mDirty;
|
|
delete oldCache;
|
|
|
|
memcpy(min, mWaveCache->min, numPixels*sizeof(float));
|
|
memcpy(max, mWaveCache->max, numPixels*sizeof(float));
|
|
memcpy(rms, mWaveCache->rms, numPixels*sizeof(float));
|
|
memcpy(bl, mWaveCache->bl, numPixels*sizeof(int));
|
|
memcpy(where, mWaveCache->where, (numPixels+1)*sizeof(sampleCount));
|
|
|
|
//find the number of OD pixels - the only way to do this is by recounting since we've lost some old cache.
|
|
mWaveCache->numODPixels = 0;
|
|
for(int j=0;j<mWaveCache->len;j++)
|
|
if(mWaveCache->bl[j]<0)
|
|
mWaveCache->numODPixels++;
|
|
|
|
isLoadingOD = mWaveCache->numODPixels>0;
|
|
mWaveCacheMutex.Unlock();
|
|
return true;
|
|
}
|
|
|
|
bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
|
|
int numPixels,
|
|
double t0, double pixelsPerSecond,
|
|
bool autocorrelation)
|
|
{
|
|
int minFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), 0L);
|
|
int maxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), 8000L);
|
|
int range = gPrefs->Read(wxT("/Spectrum/Range"), 80L);
|
|
int gain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L);
|
|
int frequencygain = gPrefs->Read(wxT("/Spectrum/FrequencyGain"), 0L);
|
|
int windowType;
|
|
int windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256);
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
int fftSkipPoints = gPrefs->Read(wxT("/Spectrum/FFTSkipPoints"), 0L);
|
|
int fftSkipPoints1 = fftSkipPoints+1;
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
int half = windowSize/2;
|
|
gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, 3);
|
|
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
// Update the FFT and window if necessary
|
|
if((mWindowType != windowType) || (mWindowSize != windowSize)
|
|
|| (hFFT == NULL) || (mWindow == NULL) || (mWindowSize != hFFT->Points*2) ) {
|
|
mWindowType = windowType;
|
|
mWindowSize = windowSize;
|
|
if(hFFT != NULL)
|
|
EndFFT(hFFT);
|
|
hFFT = InitializeFFT(mWindowSize);
|
|
if(mWindow != NULL) delete[] mWindow;
|
|
// Create the requested window function
|
|
mWindow = new float[mWindowSize];
|
|
int i;
|
|
for(i=0; i<windowSize; i++)
|
|
mWindow[i]=1.0;
|
|
WindowFunc(mWindowType, mWindowSize, mWindow);
|
|
// Scale the window function to give 0dB spectrum for 0dB sine tone
|
|
double ws=0;
|
|
for(i=0; i<windowSize; i++)
|
|
ws += mWindow[i];
|
|
if(ws > 0) {
|
|
ws = 2.0/ws;
|
|
for(i=0; i<windowSize; i++)
|
|
mWindow[i] *= ws;
|
|
}
|
|
}
|
|
#endif // EXPERIMENTAL_USE_REALFFTF
|
|
|
|
if (mSpecCache &&
|
|
mSpecCache->minFreqOld == minFreq &&
|
|
mSpecCache->maxFreqOld == maxFreq &&
|
|
mSpecCache->rangeOld == range &&
|
|
mSpecCache->gainOld == gain &&
|
|
mSpecCache->windowTypeOld == windowType &&
|
|
mSpecCache->windowSizeOld == windowSize &&
|
|
mSpecCache->frequencyGainOld == frequencygain &&
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
mSpecCache->fftSkipPointsOld == fftSkipPoints &&
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
mSpecCache->dirty == mDirty &&
|
|
mSpecCache->start == t0 &&
|
|
mSpecCache->ac == autocorrelation &&
|
|
mSpecCache->len >= numPixels &&
|
|
mSpecCache->pps == pixelsPerSecond) {
|
|
memcpy(freq, mSpecCache->freq, numPixels*half*sizeof(float));
|
|
memcpy(where, mSpecCache->where, (numPixels+1)*sizeof(sampleCount));
|
|
return false; //hit cache completely
|
|
}
|
|
|
|
SpecCache *oldCache = mSpecCache;
|
|
|
|
mSpecCache = new SpecCache(numPixels, half, autocorrelation);
|
|
mSpecCache->pps = pixelsPerSecond;
|
|
mSpecCache->start = t0;
|
|
|
|
sampleCount x;
|
|
|
|
bool *recalc = new bool[mSpecCache->len + 1];
|
|
|
|
for (x = 0; x < mSpecCache->len + 1; x++) {
|
|
recalc[x] = true;
|
|
// purposely offset the display 1/2 bin to the left (as compared
|
|
// to waveform display to properly center response of the FFT
|
|
mSpecCache->where[x] =
|
|
(sampleCount)floor((t0*mRate) + (x*mRate/pixelsPerSecond) + 1.);
|
|
}
|
|
|
|
// Optimization: if the old cache is good and overlaps
|
|
// with the current one, re-use as much of the cache as
|
|
// possible
|
|
if (oldCache->dirty == mDirty &&
|
|
oldCache->minFreqOld == minFreq &&
|
|
oldCache->maxFreqOld == maxFreq &&
|
|
oldCache->rangeOld == range &&
|
|
oldCache->gainOld == gain &&
|
|
oldCache->windowTypeOld == windowType &&
|
|
oldCache->windowSizeOld == windowSize &&
|
|
oldCache->frequencyGainOld == frequencygain &&
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
oldCache->fftSkipPointsOld == fftSkipPoints &&
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
oldCache->pps == pixelsPerSecond &&
|
|
oldCache->ac == autocorrelation &&
|
|
oldCache->where[0] < mSpecCache->where[mSpecCache->len] &&
|
|
oldCache->where[oldCache->len] > mSpecCache->where[0]) {
|
|
|
|
for (x = 0; x < mSpecCache->len; x++)
|
|
if (mSpecCache->where[x] >= oldCache->where[0] &&
|
|
mSpecCache->where[x] <= oldCache->where[oldCache->len]) {
|
|
|
|
int ox = (int) ((double (oldCache->len) *
|
|
(mSpecCache->where[x] - oldCache->where[0]))
|
|
/ (oldCache->where[oldCache->len] -
|
|
oldCache->where[0]) + 0.5);
|
|
if (ox >= 0 && ox < oldCache->len &&
|
|
mSpecCache->where[x] == oldCache->where[ox]) {
|
|
|
|
for (sampleCount i = 0; i < (sampleCount)half; i++)
|
|
mSpecCache->freq[half * x + i] =
|
|
oldCache->freq[half * ox + i];
|
|
|
|
recalc[x] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
float *buffer = new float[windowSize*fftSkipPoints1];
|
|
mSpecCache->fftSkipPointsOld = fftSkipPoints;
|
|
#else //!EXPERIMENTAL_FFT_SKIP_POINTS
|
|
float *buffer = new float[windowSize];
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
mSpecCache->minFreqOld = minFreq;
|
|
mSpecCache->maxFreqOld = maxFreq;
|
|
mSpecCache->gainOld = gain;
|
|
mSpecCache->rangeOld = range;
|
|
mSpecCache->windowTypeOld = windowType;
|
|
mSpecCache->windowSizeOld = windowSize;
|
|
mSpecCache->frequencyGainOld = frequencygain;
|
|
|
|
float *gainfactor = NULL;
|
|
if(frequencygain > 0) {
|
|
// Compute a frequency-dependant gain factor
|
|
// scaled such that 1000 Hz gets a gain of 0dB
|
|
double factor = 0.001*(double)mRate/(double)windowSize;
|
|
gainfactor = new float[half];
|
|
for(x = 0; x < half; x++) {
|
|
gainfactor[x] = frequencygain*log10(factor * x);
|
|
}
|
|
}
|
|
|
|
for (x = 0; x < mSpecCache->len; x++)
|
|
if (recalc[x]) {
|
|
|
|
sampleCount start = mSpecCache->where[x];
|
|
sampleCount len = windowSize;
|
|
sampleCount i;
|
|
|
|
if (start <= 0 || start >= mSequence->GetNumSamples()) {
|
|
|
|
for (i = 0; i < (sampleCount)half; i++)
|
|
mSpecCache->freq[half * x + i] = 0;
|
|
|
|
}
|
|
else
|
|
{
|
|
float *adj = buffer;
|
|
start -= windowSize >> 1;
|
|
|
|
if (start < 0) {
|
|
for (i = start; i < 0; i++)
|
|
*adj++ = 0;
|
|
len += start;
|
|
start = 0;
|
|
}
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
if (start + len*fftSkipPoints1 > mSequence->GetNumSamples()) {
|
|
int newlen = (mSequence->GetNumSamples() - start)/fftSkipPoints1;
|
|
for (i = newlen*fftSkipPoints1; i < (sampleCount)len*fftSkipPoints1; i++)
|
|
#else //!EXPERIMENTAL_FFT_SKIP_POINTS
|
|
if (start + len > mSequence->GetNumSamples()) {
|
|
int newlen = mSequence->GetNumSamples() - start;
|
|
for (i = newlen; i < (sampleCount)len; i++)
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
adj[i] = 0;
|
|
len = newlen;
|
|
}
|
|
|
|
if (len > 0)
|
|
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
|
|
mSequence->Get((samplePtr)adj, floatSample, start, len*fftSkipPoints1);
|
|
if (fftSkipPoints) {
|
|
// TODO: (maybe) alternatively change Get to include skipping of points
|
|
int j=0;
|
|
for (int i=0; i < len; i++) {
|
|
adj[i]=adj[j];
|
|
j+=fftSkipPoints1;
|
|
}
|
|
}
|
|
#else //!EXPERIMENTAL_FFT_SKIP_POINTS
|
|
mSequence->Get((samplePtr)adj, floatSample, start, len);
|
|
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
|
|
|
|
#ifdef EXPERIMENTAL_USE_REALFFTF
|
|
if(autocorrelation) {
|
|
ComputeSpectrum(buffer, windowSize, windowSize,
|
|
mRate, &mSpecCache->freq[half * x],
|
|
autocorrelation, windowType);
|
|
} else {
|
|
ComputeSpectrumUsingRealFFTf(buffer, hFFT, mWindow, mWindowSize, &mSpecCache->freq[half * x]);
|
|
}
|
|
#else // EXPERIMENTAL_USE_REALFFTF
|
|
ComputeSpectrum(buffer, windowSize, windowSize,
|
|
mRate, &mSpecCache->freq[half * x],
|
|
autocorrelation, windowType);
|
|
#endif // EXPERIMENTAL_USE_REALFFTF
|
|
if(gainfactor) {
|
|
// Apply a frequency-dependant gain factor
|
|
for(i=0; i<half; i++)
|
|
mSpecCache->freq[half * x + i] += gainfactor[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if(gainfactor)
|
|
delete[] gainfactor;
|
|
delete[]buffer;
|
|
delete[]recalc;
|
|
delete oldCache;
|
|
|
|
mSpecCache->dirty = mDirty;
|
|
memcpy(freq, mSpecCache->freq, numPixels*half*sizeof(float));
|
|
memcpy(where, mSpecCache->where, (numPixels+1)*sizeof(sampleCount));
|
|
return true;
|
|
}
|
|
|
|
bool WaveClip::GetMinMax(float *min, float *max,
|
|
double t0, double t1)
|
|
{
|
|
*min = float(0.0);
|
|
*max = float(0.0);
|
|
|
|
if (t0 > t1)
|
|
return false;
|
|
|
|
if (t0 == t1)
|
|
return true;
|
|
|
|
sampleCount s0, s1;
|
|
|
|
TimeToSamplesClip(t0, &s0);
|
|
TimeToSamplesClip(t1, &s1);
|
|
|
|
return mSequence->GetMinMax(s0, s1-s0, min, max);
|
|
}
|
|
|
|
bool WaveClip::GetRMS(float *rms, double t0,
|
|
double t1)
|
|
{
|
|
*rms = float(0.0);
|
|
|
|
if (t0 > t1)
|
|
return false;
|
|
|
|
if (t0 == t1)
|
|
return true;
|
|
|
|
sampleCount s0, s1;
|
|
|
|
TimeToSamplesClip(t0, &s0);
|
|
TimeToSamplesClip(t1, &s1);
|
|
|
|
return mSequence->GetRMS(s0, s1-s0, rms);
|
|
}
|
|
|
|
void WaveClip::ConvertToSampleFormat(sampleFormat format)
|
|
{
|
|
bool result;
|
|
result = mSequence->ConvertToSampleFormat(format);
|
|
MarkChanged();
|
|
wxASSERT(result);
|
|
}
|
|
|
|
void WaveClip::UpdateEnvelopeTrackLen()
|
|
{
|
|
mEnvelope->SetTrackLen(((double)mSequence->GetNumSamples()) / mRate);
|
|
}
|
|
|
|
void WaveClip::TimeToSamplesClip(double t0, sampleCount *s0) const
|
|
{
|
|
if (t0 < mOffset)
|
|
*s0 = 0;
|
|
else if (t0 > mOffset + double(mSequence->GetNumSamples())/mRate)
|
|
*s0 = mSequence->GetNumSamples();
|
|
else
|
|
*s0 = (sampleCount)floor(((t0 - mOffset) * mRate) + 0.5);
|
|
}
|
|
|
|
void WaveClip::ClearDisplayRect()
|
|
{
|
|
mDisplayRect.x = mDisplayRect.y = -1;
|
|
mDisplayRect.width = mDisplayRect.height = -1;
|
|
}
|
|
|
|
void WaveClip::SetDisplayRect(const wxRect& r)
|
|
{
|
|
mDisplayRect = r;
|
|
}
|
|
|
|
void WaveClip::GetDisplayRect(wxRect* r)
|
|
{
|
|
*r = mDisplayRect;
|
|
}
|
|
|
|
bool WaveClip::Append(samplePtr buffer, sampleFormat format,
|
|
sampleCount len, unsigned int stride /* = 1 */,
|
|
XMLWriter* blockFileLog /*=NULL*/)
|
|
{
|
|
//wxLogDebug(wxT("Append: len=%i\n"), len);
|
|
|
|
sampleCount maxBlockSize = mSequence->GetMaxBlockSize();
|
|
sampleCount blockSize = mSequence->GetIdealAppendLen();
|
|
sampleFormat seqFormat = mSequence->GetSampleFormat();
|
|
|
|
if (!mAppendBuffer)
|
|
mAppendBuffer = NewSamples(maxBlockSize, seqFormat);
|
|
|
|
for(;;) {
|
|
if (mAppendBufferLen >= blockSize) {
|
|
bool success =
|
|
mSequence->Append(mAppendBuffer, seqFormat, blockSize,
|
|
blockFileLog);
|
|
if (!success)
|
|
return false;
|
|
memmove(mAppendBuffer,
|
|
mAppendBuffer + blockSize * SAMPLE_SIZE(seqFormat),
|
|
(mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat));
|
|
mAppendBufferLen -= blockSize;
|
|
blockSize = mSequence->GetIdealAppendLen();
|
|
}
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
int toCopy = maxBlockSize - mAppendBufferLen;
|
|
if (toCopy > len)
|
|
toCopy = len;
|
|
|
|
CopySamples(buffer, format,
|
|
mAppendBuffer + mAppendBufferLen * SAMPLE_SIZE(seqFormat),
|
|
seqFormat,
|
|
toCopy,
|
|
true, // high quality
|
|
stride);
|
|
|
|
mAppendBufferLen += toCopy;
|
|
buffer += toCopy * SAMPLE_SIZE(format) * stride;
|
|
len -= toCopy;
|
|
}
|
|
|
|
UpdateEnvelopeTrackLen();
|
|
MarkChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WaveClip::AppendAlias(wxString fName, sampleCount start,
|
|
sampleCount len, int channel,bool useOD)
|
|
{
|
|
bool result = mSequence->AppendAlias(fName, start, len, channel,useOD);
|
|
if (result)
|
|
{
|
|
UpdateEnvelopeTrackLen();
|
|
MarkChanged();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool WaveClip::AppendCoded(wxString fName, sampleCount start,
|
|
sampleCount len, int channel, int decodeType)
|
|
{
|
|
bool result = mSequence->AppendCoded(fName, start, len, channel, decodeType);
|
|
if (result)
|
|
{
|
|
UpdateEnvelopeTrackLen();
|
|
MarkChanged();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool WaveClip::Flush()
|
|
{
|
|
//wxLogDebug(wxT("WaveClip::Flush"));
|
|
//wxLogDebug(wxT(" mAppendBufferLen=%i\n"), mAppendBufferLen);
|
|
//wxLogDebug(wxT(" previous sample count %i\n"), mSequence->GetNumSamples());
|
|
|
|
bool success = true;
|
|
if (mAppendBufferLen > 0) {
|
|
success = mSequence->Append(mAppendBuffer, mSequence->GetSampleFormat(), mAppendBufferLen);
|
|
if (success) {
|
|
mAppendBufferLen = 0;
|
|
UpdateEnvelopeTrackLen();
|
|
MarkChanged();
|
|
}
|
|
}
|
|
|
|
//wxLogDebug(wxT("now sample count %i\n"), mSequence->GetNumSamples());
|
|
|
|
return success;
|
|
}
|
|
|
|
bool WaveClip::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("waveclip")))
|
|
{
|
|
double dblValue;
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
else if (!wxStrcmp(tag, wxT("envelope")))
|
|
return mEnvelope;
|
|
else if (!wxStrcmp(tag, wxT("waveclip")))
|
|
{
|
|
// Nested wave clips are cut lines
|
|
WaveClip *newCutLine = new WaveClip(mSequence->GetDirManager(),
|
|
mSequence->GetSampleFormat(), mRate);
|
|
mCutLines.Append(newCutLine);
|
|
return newCutLine;
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
void WaveClip::WriteXML(XMLWriter &xmlFile)
|
|
{
|
|
xmlFile.StartTag(wxT("waveclip"));
|
|
xmlFile.WriteAttr(wxT("offset"), mOffset, 8);
|
|
|
|
mSequence->WriteXML(xmlFile);
|
|
mEnvelope->WriteXML(xmlFile);
|
|
|
|
for (WaveClipList::compatibility_iterator it=mCutLines.GetFirst(); it; it=it->GetNext())
|
|
it->GetData()->WriteXML(xmlFile);
|
|
|
|
xmlFile.EndTag(wxT("waveclip"));
|
|
}
|
|
|
|
bool WaveClip::CreateFromCopy(double t0, double t1, WaveClip* other)
|
|
{
|
|
sampleCount s0, s1;
|
|
|
|
other->TimeToSamplesClip(t0, &s0);
|
|
other->TimeToSamplesClip(t1, &s1);
|
|
|
|
Sequence* oldSequence = mSequence;
|
|
mSequence = NULL;
|
|
if (!other->mSequence->Copy(s0, s1, &mSequence))
|
|
{
|
|
mSequence = oldSequence;
|
|
return false;
|
|
}
|
|
|
|
delete oldSequence;
|
|
delete mEnvelope;
|
|
mEnvelope = new Envelope();
|
|
mEnvelope->CopyFrom(other->mEnvelope, (double)s0/mRate, (double)s1/mRate);
|
|
|
|
MarkChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WaveClip::Paste(double t0, WaveClip* other)
|
|
{
|
|
WaveClip* pastedClip;
|
|
|
|
bool clipNeedsResampling = other->mRate != mRate;
|
|
|
|
if (clipNeedsResampling)
|
|
{
|
|
// The other clip's rate is different to our's, so resample
|
|
pastedClip = new WaveClip(*other, mSequence->GetDirManager());
|
|
if (!pastedClip->Resample(mRate))
|
|
{
|
|
delete pastedClip;
|
|
return false;
|
|
}
|
|
} else
|
|
{
|
|
// No resampling needed, just use original clip without making a copy
|
|
pastedClip = other;
|
|
}
|
|
|
|
sampleCount s0;
|
|
TimeToSamplesClip(t0, &s0);
|
|
|
|
bool result = false;
|
|
|
|
if (mSequence->Paste(s0, pastedClip->mSequence))
|
|
{
|
|
MarkChanged();
|
|
mEnvelope->Paste((double)s0/mRate, pastedClip->mEnvelope);
|
|
mEnvelope->RemoveUnneededPoints();
|
|
OffsetCutLines(t0, pastedClip->GetEndTime() - pastedClip->GetStartTime());
|
|
|
|
// Paste cut lines contained in pasted clip
|
|
for (WaveClipList::compatibility_iterator it = pastedClip->mCutLines.GetFirst(); it; it=it->GetNext())
|
|
{
|
|
WaveClip* cutline = it->GetData();
|
|
WaveClip* newCutLine = new WaveClip(*cutline,
|
|
mSequence->GetDirManager());
|
|
newCutLine->Offset(t0 - mOffset);
|
|
mCutLines.Append(newCutLine);
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
|
|
if (clipNeedsResampling)
|
|
{
|
|
// Clip was constructed as a copy, so delete it
|
|
delete pastedClip;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool WaveClip::InsertSilence(double t, double len)
|
|
{
|
|
sampleCount s0;
|
|
TimeToSamplesClip(t, &s0);
|
|
sampleCount slen = (sampleCount)floor(len * mRate + 0.5);
|
|
|
|
if (!GetSequence()->InsertSilence(s0, slen))
|
|
{
|
|
wxASSERT(false);
|
|
return false;
|
|
}
|
|
OffsetCutLines(t, len);
|
|
GetEnvelope()->InsertSpace(t, len);
|
|
MarkChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WaveClip::Clear(double t0, double t1)
|
|
{
|
|
sampleCount s0, s1;
|
|
|
|
TimeToSamplesClip(t0, &s0);
|
|
TimeToSamplesClip(t1, &s1);
|
|
|
|
if (GetSequence()->Delete(s0, s1-s0))
|
|
{
|
|
// 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();
|
|
|
|
WaveClipList::compatibility_iterator nextIt;
|
|
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=nextIt)
|
|
{
|
|
nextIt = it->GetNext();
|
|
WaveClip* clip = it->GetData();
|
|
double cutlinePosition = mOffset + clip->GetOffset();
|
|
if (cutlinePosition >= t0 && cutlinePosition <= t1)
|
|
{
|
|
// This cutline is within the area, delete it
|
|
delete clip;
|
|
mCutLines.DeleteNode(it);
|
|
} else
|
|
if (cutlinePosition >= t1)
|
|
{
|
|
clip->Offset(clip_t0-clip_t1);
|
|
}
|
|
}
|
|
|
|
// Collapse envelope
|
|
GetEnvelope()->CollapseRegion(t0, t1);
|
|
if (t0 < GetStartTime())
|
|
Offset(-(GetStartTime() - t0));
|
|
|
|
MarkChanged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WaveClip::ClearAndAddCutLine(double t0, double t1)
|
|
{
|
|
if (t0 > GetEndTime() || t1 < GetStartTime())
|
|
return true; // time out of bounds
|
|
|
|
WaveClip *newClip = new WaveClip(mSequence->GetDirManager(),
|
|
mSequence->GetSampleFormat(),
|
|
mRate);
|
|
double clip_t0 = t0;
|
|
double clip_t1 = t1;
|
|
if (clip_t0 < GetStartTime())
|
|
clip_t0 = GetStartTime();
|
|
if (clip_t1 > GetEndTime())
|
|
clip_t1 = GetEndTime();
|
|
|
|
if (!newClip->CreateFromCopy(clip_t0, clip_t1, this))
|
|
return false;
|
|
newClip->SetOffset(clip_t0-mOffset);
|
|
|
|
// Sort out cutlines that belong to the new cutline
|
|
WaveClipList::compatibility_iterator nextIt;
|
|
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=nextIt)
|
|
{
|
|
nextIt = it->GetNext();
|
|
WaveClip* clip = it->GetData();
|
|
double cutlinePosition = mOffset + clip->GetOffset();
|
|
if (cutlinePosition >= t0 && cutlinePosition <= t1)
|
|
{
|
|
clip->SetOffset(cutlinePosition - newClip->GetOffset() - mOffset);
|
|
newClip->mCutLines.Append(clip);
|
|
mCutLines.DeleteNode(it);
|
|
} else
|
|
if (cutlinePosition >= t1)
|
|
{
|
|
clip->Offset(clip_t0-clip_t1);
|
|
}
|
|
}
|
|
|
|
// Clear actual audio data
|
|
sampleCount s0, s1;
|
|
|
|
TimeToSamplesClip(t0, &s0);
|
|
TimeToSamplesClip(t1, &s1);
|
|
|
|
if (GetSequence()->Delete(s0, s1-s0))
|
|
{
|
|
// Collapse envelope
|
|
GetEnvelope()->CollapseRegion(t0, t1);
|
|
if (t0 < GetStartTime())
|
|
Offset(-(GetStartTime() - t0));
|
|
|
|
MarkChanged();
|
|
|
|
mCutLines.Append(newClip);
|
|
return true;
|
|
} else
|
|
{
|
|
delete newClip;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool WaveClip::FindCutLine(double cutLinePosition,
|
|
double* cutlineStart /* = NULL */,
|
|
double* cutlineEnd /* = NULL */)
|
|
{
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
{
|
|
WaveClip* cutline = it->GetData();
|
|
if (fabs(mOffset + cutline->GetOffset() - cutLinePosition) < 0.0001)
|
|
{
|
|
if (cutlineStart)
|
|
*cutlineStart = mOffset+cutline->GetStartTime();
|
|
if (cutlineEnd)
|
|
*cutlineEnd = mOffset+cutline->GetEndTime();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WaveClip::ExpandCutLine(double cutLinePosition)
|
|
{
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
{
|
|
WaveClip* cutline = it->GetData();
|
|
if (fabs(mOffset + cutline->GetOffset() - cutLinePosition) < 0.0001)
|
|
{
|
|
if (!Paste(mOffset+cutline->GetOffset(), cutline))
|
|
return false;
|
|
delete cutline;
|
|
mCutLines.DeleteNode(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WaveClip::RemoveCutLine(double cutLinePosition)
|
|
{
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
{
|
|
if (fabs(mOffset + it->GetData()->GetOffset() - cutLinePosition) < 0.0001)
|
|
{
|
|
delete it->GetData();
|
|
mCutLines.DeleteNode(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void WaveClip::RemoveAllCutLines()
|
|
{
|
|
while (!mCutLines.IsEmpty())
|
|
{
|
|
WaveClipList::compatibility_iterator head = mCutLines.GetFirst();
|
|
delete head->GetData();
|
|
mCutLines.DeleteNode(head);
|
|
}
|
|
}
|
|
|
|
void WaveClip::OffsetCutLines(double t0, double len)
|
|
{
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
{
|
|
WaveClip* cutLine = it->GetData();
|
|
if (mOffset + cutLine->GetOffset() >= t0)
|
|
cutLine->Offset(len);
|
|
}
|
|
}
|
|
|
|
void WaveClip::Lock()
|
|
{
|
|
GetSequence()->Lock();
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
it->GetData()->Lock();
|
|
}
|
|
|
|
void WaveClip::CloseLock()
|
|
{
|
|
GetSequence()->CloseLock();
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
it->GetData()->Lock();
|
|
}
|
|
|
|
void WaveClip::Unlock()
|
|
{
|
|
GetSequence()->Unlock();
|
|
for (WaveClipList::compatibility_iterator it = mCutLines.GetFirst(); it; it=it->GetNext())
|
|
it->GetData()->Unlock();
|
|
}
|
|
|
|
void WaveClip::SetRate(int rate)
|
|
{
|
|
mRate = rate;
|
|
UpdateEnvelopeTrackLen();
|
|
MarkChanged();
|
|
}
|
|
|
|
bool WaveClip::Resample(int rate, ProgressDialog *progress)
|
|
{
|
|
if (rate == mRate)
|
|
return true; // Nothing to do
|
|
|
|
double factor = (double)rate / (double)mRate;
|
|
::Resample* resample = new ::Resample(true, factor, factor);
|
|
|
|
int bufsize = 65536;
|
|
float* inBuffer = new float[bufsize];
|
|
float* outBuffer = new float[bufsize];
|
|
sampleCount pos = 0;
|
|
bool error = false;
|
|
int outGenerated = 0;
|
|
sampleCount numSamples = mSequence->GetNumSamples();
|
|
|
|
Sequence* newSequence =
|
|
new Sequence(mSequence->GetDirManager(), 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)
|
|
{
|
|
int inLen = numSamples - pos;
|
|
if (inLen > bufsize)
|
|
inLen = bufsize;
|
|
|
|
bool isLast = ((pos + inLen) == numSamples);
|
|
|
|
if (!mSequence->Get((samplePtr)inBuffer, floatSample, pos, inLen))
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
|
|
int inBufferUsed = 0;
|
|
outGenerated = resample->Process(factor, inBuffer, inLen, isLast,
|
|
&inBufferUsed, outBuffer, bufsize);
|
|
|
|
pos += inBufferUsed;
|
|
|
|
if (outGenerated < 0)
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
|
|
if (!newSequence->Append((samplePtr)outBuffer, floatSample,
|
|
outGenerated))
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
|
|
if (progress)
|
|
{
|
|
int updateResult = progress->Update(pos, numSamples);
|
|
error = (updateResult != eProgressSuccess);
|
|
if (error)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete[] inBuffer;
|
|
delete[] outBuffer;
|
|
delete resample;
|
|
|
|
if (error)
|
|
{
|
|
delete newSequence;
|
|
} else
|
|
{
|
|
delete mSequence;
|
|
mSequence = newSequence;
|
|
mRate = rate;
|
|
|
|
// Invalidate wave display cache
|
|
if (mWaveCache)
|
|
{
|
|
delete mWaveCache;
|
|
mWaveCache = NULL;
|
|
}
|
|
mWaveCache = new WaveCache(1);
|
|
// Invalidate the spectrum display cache
|
|
if (mSpecCache)
|
|
delete mSpecCache;
|
|
mSpecCache = new SpecCache(1, 1, false);
|
|
}
|
|
|
|
return !error;
|
|
}
|