/********************************************************************** 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 #include #include #include "Spectrum.h" #include "Prefs.h" #include "WaveClip.h" #include "Envelope.h" #include "Resample.h" #include "Project.h" #include 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;istart <= 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;istart <= 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; 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; iPoints*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;iPoints;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;iGetNumInvalidRegions();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;jbl[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;jbl[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;iGetNumInvalidRegions();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; awhere[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; iwhere[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 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;jlen;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 0) { ws = 2.0/ws; for(i=0; iminFreqOld == 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; ifreq[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"), 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"), mAppendBufferLen); //wxLogDebug(wxT(" previous sample count %i"), 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"), 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; }