Support backwards play, a requirement for scrubbing

Uncomment the line at the top of ControlToolBar::PlayPlayRegion to play
everything backwards and test it

It even works correctly with a time track
This commit is contained in:
Paul-Licameli 2015-04-16 17:35:58 -04:00
parent 2b85d0edb4
commit 5abfd25a34
8 changed files with 256 additions and 109 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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++;
}
}
}
}

View File

@ -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)); }

View File

@ -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; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
if (backwards) {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos - (slen - 1), slen);
track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
ReverseSamples((samplePtr)mFloatBuffer, floatSample, 0, slen);
*pos -= slen;
}
else {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen);
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
*pos += slen;
}
for(c=0; c<mNumChannels; c++)
if (mApplyTrackGains)
@ -579,8 +623,6 @@ sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track,
MixBuffers(mNumChannels, channelFlags, mGains,
(samplePtr)mFloatBuffer, mTemp, slen, mInterleaved);
*pos += slen;
return slen;
}
@ -639,7 +681,9 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
maxOut = out;
double t = (double)mSamplePos[i] / (double)track->GetRate();
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; i<mNumInputTracks; i++) {
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mTime);

View File

@ -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,

View File

@ -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.

View File

@ -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;