diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index a0a310d5d..48b3f2189 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -79,6 +79,7 @@ the speed control. In a separate algorithm, the audio callback updates mTime by (frames / samplerate) * factor, where factor reflects the speed at mTime. This effectively integrates speed to get position. + Negative speeds are allowed too, for instance in scrubbing. \par Midi Time MIDI is not warped according to the speed control. This might be @@ -1213,10 +1214,13 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, // with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy // (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug mWarpedTime = 0.0; - if(mTimeTrack) + if (mTimeTrack) + // Following gives negative when mT0 > mT1 mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1); else mWarpedLength = mT1 - mT0; + // PRL allow backwards play + mWarpedLength = abs(mWarpedLength); // // The RingBuffer sizes, and the max amount of the buffer to @@ -1999,6 +2003,15 @@ bool AudioIO::IsMonitoring() return ( mPortStreamV19 && mStreamToken==0 ); } +double AudioIO::LimitStreamTime(double absoluteTime) const +{ + // Allows for forward or backward play + if (ReversedTime()) + return std::max(mT1, std::min(mT0, absoluteTime)); + else + return std::max(mT0, std::min(mT1, absoluteTime)); +} + double AudioIO::NormalizeStreamTime(double absoluteTime) const { // dmazzoni: This function is needed for two reasons: @@ -2013,13 +2026,7 @@ double AudioIO::NormalizeStreamTime(double absoluteTime) const // mode. In this case, we should jump over a defined "gap" in the // audio. - // msmeyer: Just to be sure, the returned stream time should - // never be smaller than the actual start time. - if (absoluteTime < mT0) - absoluteTime = mT0; - - if (absoluteTime > mT1) - absoluteTime = mT1; + absoluteTime = LimitStreamTime(absoluteTime); if (mCutPreviewGapLen > 0) { @@ -2863,6 +2870,7 @@ void AudioIO::FillBuffers() warpedSamples = mPlaybackMixers[i]->GetBuffer(); mPlaybackBuffers[i]->Put(warpedSamples, floatSample, processed); } + //if looping and processed is less than the full chunk/block/buffer that gets pulled from //other longer tracks, then we still need to advance the ring buffers or //we'll trip up on ourselves when we start them back up again. @@ -3561,17 +3569,20 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, // Calculate the new time position gAudioIO->mTime += gAudioIO->mSeek; - if (gAudioIO->mTime < gAudioIO->mT0) - gAudioIO->mTime = gAudioIO->mT0; - else if (gAudioIO->mTime > gAudioIO->mT1) - gAudioIO->mTime = gAudioIO->mT1; + gAudioIO->mTime = gAudioIO->LimitStreamTime(gAudioIO->mTime); gAudioIO->mSeek = 0.0; // Reset mixer positions and flush buffers for all tracks if(gAudioIO->mTimeTrack) - gAudioIO->mWarpedTime = gAudioIO->mTimeTrack->ComputeWarpedLength(gAudioIO->mT0, gAudioIO->mTime); + // Following gives negative when mT0 > mTime + gAudioIO->mWarpedTime = + gAudioIO->mTimeTrack->ComputeWarpedLength + (gAudioIO->mT0, gAudioIO->mTime); else gAudioIO->mWarpedTime = gAudioIO->mTime - gAudioIO->mT0; + gAudioIO->mWarpedTime = abs(gAudioIO->mWarpedTime); + + // Reset mixer positions and flush buffers for all tracks for (i = 0; i < (unsigned int)numPlaybackTracks; i++) { gAudioIO->mPlaybackMixers[i]->Reposition(gAudioIO->mTime); @@ -3703,10 +3714,12 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, // the end, then we've actually finished playing the entire // selection. // msmeyer: We never finish if we are playing looped - if (len == 0 && gAudioIO->mTime >= gAudioIO->mT1 && - !gAudioIO->mPlayLooped) - { - callbackReturn = paComplete; + if (len == 0 && + !gAudioIO->mPlayLooped) { + if ((gAudioIO->ReversedTime() + ? gAudioIO->mTime <= gAudioIO->mT1 + : gAudioIO->mTime >= gAudioIO->mT1)) + callbackReturn = paComplete; } if (cut) // no samples to process, they've been discarded @@ -3852,20 +3865,30 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, } // Update the current time position - if (gAudioIO->mTimeTrack) { - // MB: this is why SolveWarpedLength is needed :) - gAudioIO->mTime = gAudioIO->mTimeTrack->SolveWarpedLength(gAudioIO->mTime, framesPerBuffer / gAudioIO->mRate); - } else { - gAudioIO->mTime += framesPerBuffer / gAudioIO->mRate; + { + double delta = framesPerBuffer / gAudioIO->mRate; + if (gAudioIO->ReversedTime()) + delta *= -1.0; + if (gAudioIO->mTimeTrack) + // MB: this is why SolveWarpedLength is needed :) + gAudioIO->mTime = + gAudioIO->mTimeTrack->SolveWarpedLength(gAudioIO->mTime, delta); + else + gAudioIO->mTime += delta; } // Wrap to start if looping - while (gAudioIO->mPlayLooped && gAudioIO->mTime >= gAudioIO->mT1) + if (gAudioIO->mPlayLooped) { - // LL: This is not exactly right, but I'm at my wits end trying to - // figure it out. Feel free to fix it. :-) - // MB: it's much easier than you think, mTime isn't warped at all! - gAudioIO->mTime -= gAudioIO->mT1 - gAudioIO->mT0; + while (gAudioIO->ReversedTime() + ? gAudioIO->mTime <= gAudioIO->mT1 + : gAudioIO->mTime >= gAudioIO->mT1) + { + // LL: This is not exactly right, but I'm at my wits end trying to + // figure it out. Feel free to fix it. :-) + // MB: it's much easier than you think, mTime isn't warped at all! + gAudioIO->mTime -= gAudioIO->mT1 - gAudioIO->mT0; + } } // Record the reported latency from PortAudio. diff --git a/src/AudioIO.h b/src/AudioIO.h index 39af256a1..4c49befec 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -435,6 +435,12 @@ private: /** \brief How many sample rates to try */ static const int NumRatesToTry; + bool ReversedTime() const + { + return mT1 < mT0; + } + double LimitStreamTime(double absoluteTime) const; + double NormalizeStreamTime(double absoluteTime) const; /** \brief Clean up after StartStream if it fails. diff --git a/src/Envelope.cpp b/src/Envelope.cpp index a5658f047..b4892292e 100644 --- a/src/Envelope.cpp +++ b/src/Envelope.cpp @@ -1430,31 +1430,42 @@ double Envelope::SolveIntegralOfInverse( double t0, double area ) { if(area == 0.0) return t0; - if(area < 0.0) - { - fprintf( stderr, "SolveIntegralOfInverse called with negative area, this is not supported!\n" ); - return t0; - } unsigned int count = mEnv.Count(); if(count == 0) // 'empty' envelope return t0 + area * mDefaultValue; double lastT, lastVal; - unsigned int i; // this is the next point to check + int i; // this is the next point to check if(t0 < mEnv[0]->GetT()) // t0 preceding the first point { - i = 1; - lastT = mEnv[0]->GetT(); - lastVal = mEnv[0]->GetVal(); - double added = (lastT - t0) / lastVal; - if(added >= area) + if (area < 0) { return t0 + area * mEnv[0]->GetVal(); - area -= added; + } + else { + i = 1; + lastT = mEnv[0]->GetT(); + lastVal = mEnv[0]->GetVal(); + double added = (lastT - t0) / lastVal; + if(added >= area) + return t0 + area * mEnv[0]->GetVal(); + area -= added; + } } else if(t0 >= mEnv[count - 1]->GetT()) // t0 following the last point { - return t0 + area * mEnv[count - 1]->GetVal(); + if (area < 0) { + i = count - 2; + lastT = mEnv[count - 1]->GetT(); + lastVal = mEnv[count - 1]->GetVal(); + double added = (lastT - t0) / lastVal; // negative + if(added <= area) + return t0 + area * mEnv[count - 1]->GetVal(); + area -= added; + } + else { + return t0 + area * mEnv[count - 1]->GetVal(); + } } else // t0 enclosed by points { @@ -1463,25 +1474,52 @@ double Envelope::SolveIntegralOfInverse( double t0, double area ) BinarySearchForTime(lo, hi, t0); lastVal = InterpolatePoints(mEnv[lo]->GetVal(), mEnv[hi]->GetVal(), (t0 - mEnv[lo]->GetT()) / (mEnv[hi]->GetT() - mEnv[lo]->GetT()), mDB); lastT = t0; - i = hi; // the point immediately after t0. + if (area < 0) + i = lo; + else + i = hi; // the point immediately after t0. } - // loop through the rest of the envelope points until we get to t1 - while (1) - { - if(i >= count) // the requested range extends beyond the last point + if (area < 0) { + // loop BACKWARDS through the rest of the envelope points until we get to t1 + // (which is less than t0) + while (1) { - return lastT + area * lastVal; + if(i < 0) // the requested range extends beyond the leftmost point + { + return lastT + area * lastVal; + } + else + { + double added = + -IntegrateInverseInterpolated(mEnv[i]->GetVal(), lastVal, lastT - mEnv[i]->GetT(), mDB); + if(added <= area) + return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), lastT - mEnv[i]->GetT(), -area, mDB); + area -= added; + lastT = mEnv[i]->GetT(); + lastVal = mEnv[i]->GetVal(); + --i; + } } - else + } + else { + // loop through the rest of the envelope points until we get to t1 + while (1) { - double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB); - if(added >= area) - return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, area, mDB); - area -= added; - lastT = mEnv[i]->GetT(); - lastVal = mEnv[i]->GetVal(); - i++; + if(i >= count) // the requested range extends beyond the last point + { + return lastT + area * lastVal; + } + else + { + double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB); + if(added >= area) + return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, area, mDB); + area -= added; + lastT = mEnv[i]->GetT(); + lastVal = mEnv[i]->GetVal(); + i++; + } } } } diff --git a/src/Envelope.h b/src/Envelope.h index 1a33abc0f..481657bd4 100644 --- a/src/Envelope.h +++ b/src/Envelope.h @@ -99,8 +99,8 @@ class Envelope : public XMLTagHandler { void Flatten(double value); int GetDragPoint(void) {return mDragPoint;} - double GetMinValue() { return mMinValue; } - double GetMaxValue() { return mMaxValue; } + double GetMinValue() const { return mMinValue; } + double GetMaxValue() const { return mMaxValue; } void SetRange(double minValue, double maxValue); double ClampValue(double value) { return std::max(mMinValue, std::min(mMaxValue, value)); } diff --git a/src/Mix.cpp b/src/Mix.cpp index cad92c516..16f26069d 100644 --- a/src/Mix.cpp +++ b/src/Mix.cpp @@ -423,10 +423,9 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, int *queueStart, int *queueLen, Resample * pResample) { - double trackRate = track->GetRate(); - double initialWarp = mRate / trackRate; - double tstep = 1.0 / trackRate; - double t = (*pos - *queueLen) / trackRate; + const double trackRate = track->GetRate(); + const double initialWarp = mRate / trackRate; + const double tstep = 1.0 / trackRate; int sampleSize = SAMPLE_SIZE(floatSample); sampleCount out = 0; @@ -443,14 +442,15 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, */ // Find the last sample - sampleCount endPos; double endTime = track->GetEndTime(); - if (endTime > mT1) { - endPos = track->TimeToLongSamples(mT1); - } - else { - endPos = track->TimeToLongSamples(endTime); - } + double startTime = track->GetStartTime(); + const sampleCount endPos = + track->TimeToLongSamples(std::max(startTime, std::min(endTime, mT1))); + const sampleCount startPos = + track->TimeToLongSamples(std::max(startTime, std::min(endTime, mT0))); + const bool backwards = (endPos < startPos); + // Find the time corresponding to the start of the queue, for use with time track + double t = (*pos + (backwards ? *queueLen : - *queueLen)) / trackRate; while (out < mMaxOut) { if (*queueLen < mProcessLen) { @@ -458,31 +458,48 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, memmove(queue, &queue[*queueStart], (*queueLen) * sampleSize); *queueStart = 0; - int getLen = mQueueMaxLen - *queueLen; + int getLen = + std::min((backwards ? *pos - endPos : endPos - *pos), + sampleCount(mQueueMaxLen - *queueLen)); - // Constrain - if (*pos + getLen > endPos) { - getLen = endPos - *pos; - } - - // Nothing to do if past end of track + // Nothing to do if past end of play interval if (getLen > 0) { - track->Get((samplePtr)&queue[*queueLen], - floatSample, - *pos, - getLen); + if (backwards) { + track->Get((samplePtr)&queue[*queueLen], + floatSample, + *pos - (getLen - 1), + getLen); - track->GetEnvelopeValues(mEnvValues, - getLen, - (*pos) / trackRate, - tstep); + track->GetEnvelopeValues(mEnvValues, + getLen, + (*pos - (getLen- 1)) / trackRate, + tstep); + + *pos -= getLen; + } + else { + track->Get((samplePtr)&queue[*queueLen], + floatSample, + *pos, + getLen); + + track->GetEnvelopeValues(mEnvValues, + getLen, + (*pos) / trackRate, + tstep); + + *pos += getLen; + } for (int i = 0; i < getLen; i++) { queue[(*queueLen) + i] *= mEnvValues[i]; } + if (backwards) + ReverseSamples((samplePtr)&queue[0], floatSample, + *queueStart, getLen); + *queueLen += getLen; - *pos += getLen; } } @@ -499,8 +516,13 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, // as a result of this the warp factor may be slightly wrong, so AudioIO will stop too soon // or too late (resulting in missing sound or inserted silence). This can't be fixed // without changing the way the resampler works, because the number of input samples that will be used - // is unpredictable. Maybe it can be compensated lated though. - factor *= mTimeTrack->ComputeWarpFactor(t, t + (double)thisProcessLen / trackRate); + // is unpredictable. Maybe it can be compensated later though. + if (backwards) + factor *= mTimeTrack->ComputeWarpFactor + (t - (double)thisProcessLen / trackRate + tstep, t + tstep); + else + factor *= mTimeTrack->ComputeWarpFactor + (t, t + (double)thisProcessLen / trackRate); } int input_used; @@ -519,7 +541,7 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, *queueStart += input_used; *queueLen -= input_used; out += outgen; - t += (input_used / trackRate); + t += ((backwards ? -input_used : input_used) / trackRate); if (last) { break; @@ -551,24 +573,46 @@ sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track, { int slen = mMaxOut; int c; - double t = *pos / track->GetRate(); - double trackEndTime = track->GetEndTime(); - double tEnd = trackEndTime > mT1 ? mT1 : trackEndTime; + const double t = *pos / track->GetRate(); + const double trackEndTime = track->GetEndTime(); + const double trackStartTime = track->GetStartTime(); + const double tEnd = std::max(trackStartTime, std::min(trackEndTime, mT1)); + const double tStart = std::max(trackStartTime, std::min(trackEndTime, mT0)); + const bool backwards = (tEnd < tStart); //don't process if we're at the end of the selection or track. - if (t>=tEnd) + if ((backwards ? t <= tEnd : t >= tEnd)) return 0; //if we're about to approach the end of the track or selection, figure out how much we need to grab - if (t + slen/track->GetRate() > tEnd) - slen = (int)((tEnd - t) * track->GetRate() + 0.5); + if (backwards) { + if (t - slen/track->GetRate() < tEnd) + slen = (int)((t - tEnd) * track->GetRate() + 0.5); + } + else { + if (t + slen/track->GetRate() > tEnd) + slen = (int)((tEnd - t) * track->GetRate() + 0.5); + } if (slen > mMaxOut) slen = mMaxOut; - track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen); - track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate); - for(int i=0; iGet((samplePtr)mFloatBuffer, floatSample, *pos - (slen - 1), slen); + track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate); + for(int i=0; iGet((samplePtr)mFloatBuffer, floatSample, *pos, slen); + track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate); + for(int i=0; iGetRate(); - if(t > mTime) + if (mT0 > mT1) + mTime = std::max(t, mT1); + else mTime = std::min(t, mT1); } if(mInterleaved) { @@ -707,10 +751,11 @@ void Mixer::Reposition(double t) int i; mTime = t; - if( mTime < mT0 ) - mTime = mT0; - if( mTime > mT1 ) - mTime = mT1; + const bool backwards = (mT1 < mT0); + if (backwards) + mTime = std::max(mT1, (std::min(mT0, mTime))); + else + mTime = std::max(mT0, (std::min(mT1, mTime))); for(i=0; iTimeToLongSamples(mTime); diff --git a/src/SampleFormat.cpp b/src/SampleFormat.cpp index 7ea934079..1ac38f81c 100644 --- a/src/SampleFormat.cpp +++ b/src/SampleFormat.cpp @@ -92,6 +92,24 @@ void ClearSamples(samplePtr src, sampleFormat format, memset(src + start*size, 0, len*size); } +void ReverseSamples(samplePtr src, sampleFormat format, + int start, int len) +{ + int size = SAMPLE_SIZE(format); + samplePtr first = src + start * size; + samplePtr last = src + (start + len - 1) * size; + enum { fixedSize = SAMPLE_SIZE(floatSample) }; + wxASSERT(size <= fixedSize); + char temp[fixedSize]; + while (first < last) { + memcpy(temp, first, size); + memcpy(first, last, size); + memcpy(last, temp, size); + first += size; + last -= size; + } +} + void CopySamples(samplePtr src, sampleFormat srcFormat, samplePtr dst, sampleFormat dstFormat, unsigned int len, diff --git a/src/SampleFormat.h b/src/SampleFormat.h index d2b9e8e04..6d0dd9902 100644 --- a/src/SampleFormat.h +++ b/src/SampleFormat.h @@ -70,6 +70,9 @@ void CopySamplesNoDither(samplePtr src, sampleFormat srcFormat, void ClearSamples(samplePtr buffer, sampleFormat format, int start, int len); +void ReverseSamples(samplePtr buffer, sampleFormat format, + int start, int len); + // // This must be called on startup and everytime new ditherers // are set in preferences. diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 641869266..ec11310e9 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -473,13 +473,17 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, bool cutpreview, /* = false */ bool backwards /* = false */) { + // Uncomment this for laughs! + // backwards = true; + double t0 = selectedRegion.t0(); double t1 = selectedRegion.t1(); // SelectedRegion guarantees t0 <= t1, so we need another boolean argument // to indicate backwards play. const bool looped = options.playLooped; - wxASSERT(! backwards); + if (backwards) + std::swap(t0, t1); SetPlay(true, looped, cutpreview); @@ -555,7 +559,9 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, t1 = t->GetEndTime(); } else { - // always t0 < t1 right? + // maybe t1 < t0, with backwards scrubbing for instance + if (backwards) + std::swap(t0, t1); // the set intersection between the play region and the // valid range maximum of lower bounds @@ -579,6 +585,9 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, t0 = maxofmins; t1 = minofmaxs; } + + if (backwards) + std::swap(t0, t1); } // Can't play before 0...either shifted or latency corrected tracks @@ -589,14 +598,19 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, int token = -1; bool success = false; - if (t1 > t0) { + if (t1 != t0) { if (cutpreview) { + const double tless = std::min(t0, t1); + const double tgreater = std::max(t0, t1); double beforeLen, afterLen; gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0); gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0); - double tcp0 = t0-beforeLen; - double tcp1 = (t1+afterLen) - (t1-t0); - SetupCutPreviewTracks(tcp0, t0, t1, tcp1); + double tcp0 = tless-beforeLen; + double diff = tgreater - tless; + double tcp1 = (tgreater+afterLen) - diff; + SetupCutPreviewTracks(tcp0, tless, tgreater, tcp1); + if (backwards) + std::swap(tcp0, tcp1); if (mCutPreviewTracks) { AudioIOStartStreamOptions myOptions = options;