From 55be06c9e4505dba5b9bd57295167032c296153d Mon Sep 17 00:00:00 2001 From: richardash1981 Date: Wed, 19 Dec 2012 21:49:25 +0000 Subject: [PATCH] commit a large patch by Maarten Baert maarten-baerthotmailcom to fix and improve time track support. Several fix-me issues remain but none are new with this patch. --- src/AudioIO.cpp | 79 +++---- src/AudioIO.h | 14 +- src/Envelope.cpp | 314 +++++++++++++++++++------- src/Envelope.h | 21 +- src/Mix.cpp | 76 +++---- src/Mix.h | 18 +- src/Resample.cpp | 22 ++ src/Resample.h | 4 + src/TimeTrack.cpp | 174 +++++++------- src/TimeTrack.h | 48 ++-- src/TrackArtist.cpp | 50 +++- src/TrackPanel.cpp | 84 +++++-- src/TrackPanel.h | 13 +- src/export/Export.cpp | 21 +- src/toolbars/TranscriptionToolBar.cpp | 18 +- src/widgets/Ruler.cpp | 67 +++--- 16 files changed, 617 insertions(+), 406 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 59c219a61..f4e5184e8 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1167,7 +1167,6 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, mOutputMeter = NULL; mRate = sampleRate; mT0 = t0; - mT = t0; mT1 = t1; mTime = t0; mSeek = 0; @@ -1185,11 +1184,13 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, mCaptureBuffers = NULL; mResample = NULL; - double factor = 1.0; - if (mTimeTrack) - factor = mTimeTrack->ComputeWarpFactor(mT0, mT1); - - mWarpedT1 = factor >= 1 ? mT1 : mT0 + ((mT1 - mT0) / factor); + // 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) + mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1); + else + mWarpedLength = mT1 - mT0; // // The RingBuffer sizes, and the max amount of the buffer to @@ -1298,8 +1299,9 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, { mPlaybackBuffers[i] = new RingBuffer(floatSample, playbackBufferSize); + // MB: use normal time for the end time, not warped time! mPlaybackMixers[i] = new Mixer(1, &mPlaybackTracks[i], - mTimeTrack, mT0, mWarpedT1, 1, + mTimeTrack, mT0, mT1, 1, playbackMixBufferSize, false, mRate, floatSample, false); mPlaybackMixers[i]->ApplyTrackGains(false); @@ -2604,7 +2606,8 @@ void AudioIO::FillBuffers() // if we hit this code during the PortAudio callback. To keep // things simple, we only write as much data as is vacant in // ALL buffers, and advance the global time by that much. - int commonlyAvail = GetCommonlyAvailPlayback(); + // MB: subtract a few samples because the code below has rounding errors + int commonlyAvail = GetCommonlyAvailPlayback() - 10; // // Determine how much this will globally advance playback time @@ -2621,13 +2624,13 @@ void AudioIO::FillBuffers() // region - then we should just fill the buffer. // if (secsAvail >= mMaxPlaybackSecsToCopy || - (!mPlayLooped && (secsAvail > 0 && mT+secsAvail >= mWarpedT1))) + (!mPlayLooped && (secsAvail > 0 && mWarpedTime+secsAvail >= mWarpedLength))) { // Limit maximum buffer size (increases performance) if (secsAvail > mMaxPlaybackSecsToCopy) secsAvail = mMaxPlaybackSecsToCopy; - double deltat; + double deltat; // this is warped time // msmeyer: When playing a very short selection in looped // mode, the selection must be copied to the buffer multiple @@ -2635,13 +2638,17 @@ void AudioIO::FillBuffers() // This is the purpose of this loop. do { deltat = secsAvail; - if( mT + deltat > mWarpedT1 ) + if( mWarpedTime + deltat > mWarpedLength ) { - deltat = mWarpedT1 - mT; - if( deltat < 0.0 ) + deltat = mWarpedLength - mWarpedTime; + mWarpedTime = mWarpedLength; + if( deltat < 0.0 ) // this should never happen deltat = 0.0; } - mT += deltat; + else + { + mWarpedTime += deltat; + } secsAvail -= deltat; @@ -2655,7 +2662,7 @@ void AudioIO::FillBuffers() //don't do anything if we have no length. In particular, Process() will fail an wxAssert //that causes a crash since this is not the GUI thread and wxASSERT is a GUI call. if(deltat > 0.0) - { + { processed = mPlaybackMixers[i]->Process(lrint(deltat * mRate)); warpedSamples = mPlaybackMixers[i]->GetBuffer(); mPlaybackBuffers[i]->Put(warpedSamples, floatSample, processed); @@ -2675,17 +2682,17 @@ void AudioIO::FillBuffers() mSilentBuf = NewSamples(mLastSilentBufSize, floatSample); ClearSamples(mSilentBuf, floatSample, 0, mLastSilentBufSize); } - mPlaybackBuffers[i]->Put(mSilentBuf, floatSample, lrint(deltat * mRate) - processed); + mPlaybackBuffers[i]->Put(mSilentBuf, floatSample, lrint(deltat * mRate) - processed); } } // msmeyer: If playing looped, check if we are at the end of the buffer // and if yes, restart from the beginning. - if (mPlayLooped && mT >= mWarpedT1) + if (mPlayLooped && mWarpedTime >= mWarpedLength) { for (i = 0; i < mPlaybackTracks.GetCount(); i++) mPlaybackMixers[i]->Restart(); - mT = mT0; + mWarpedTime = 0.0; } } while (mPlayLooped && secsAvail > 0 && deltat > 0); @@ -3359,10 +3366,13 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, gAudioIO->mSeek = 0.0; // Reset mixer positions and flush buffers for all tracks - gAudioIO->mT = gAudioIO->mT0 + ((gAudioIO->mTime - gAudioIO->mT0)); + if(gAudioIO->mTimeTrack) + gAudioIO->mWarpedTime = gAudioIO->mTimeTrack->ComputeWarpedLength(gAudioIO->mT0, gAudioIO->mTime); + else + gAudioIO->mWarpedTime = gAudioIO->mTime - gAudioIO->mT0; for (i = 0; i < (unsigned int)numPlaybackTracks; i++) { - gAudioIO->mPlaybackMixers[i]->Reposition(gAudioIO->mT); + gAudioIO->mPlaybackMixers[i]->Reposition(gAudioIO->mTime); gAudioIO->mPlaybackBuffers[i]->Discard(gAudioIO->mPlaybackBuffers[i]->AvailForGet()); } @@ -3587,15 +3597,12 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, } } - // Calcuate the warp factor for this time position - double factor = 1.0; + // Update the current time position if (gAudioIO->mTimeTrack) { - factor = gAudioIO->mTimeTrack->GetEnvelope()->GetValue(gAudioIO->mTime); - factor = (gAudioIO->mTimeTrack->GetRangeLower() * - (1 - factor) + - factor * - gAudioIO->mTimeTrack->GetRangeUpper()) / - 100.0; + // MB: this is why SolveWarpedLength is needed :) + gAudioIO->mTime = gAudioIO->mTimeTrack->SolveWarpedLength(gAudioIO->mTime, framesPerBuffer / gAudioIO->mRate); + } else { + gAudioIO->mTime += framesPerBuffer / gAudioIO->mRate; } // Wrap to start if looping @@ -3603,12 +3610,10 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, { // LL: This is not exactly right, but I'm at my wits end trying to // figure it out. Feel free to fix it. :-) - gAudioIO->mTime = gAudioIO->mT0 - ((gAudioIO->mTime - gAudioIO->mT1) * factor); + // MB: it's much easier than you think, mTime isn't warped at all! + gAudioIO->mTime -= gAudioIO->mT1 - gAudioIO->mT0; } - // Update the current time position - gAudioIO->mTime += ((framesPerBuffer / gAudioIO->mRate) * factor); - // Record the reported latency from PortAudio. // TODO: Don't recalculate this with every callback? @@ -3705,13 +3710,3 @@ int compareTime( const void* a, const void* b ) } #endif -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 7ee3c9aa-b58b-4069-8a07-8866f2303963 diff --git a/src/AudioIO.h b/src/AudioIO.h index afc068830..289191c58 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -486,11 +486,11 @@ private: static int mNextStreamToken; double mFactor; double mRate; - double mT; double mT0; // playback starts at offset of mT0 double mT1; // and ends at offset of mT1 double mTime; // current time position during playback - double mWarpedT1; + double mWarpedTime; // current time after warping, starting at zero (unlike mTime) + double mWarpedLength; // total length after warping double mSeek; double mPlaybackRingBufferSecs; double mCaptureRingBufferSecs; @@ -598,13 +598,3 @@ private: #endif -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 5b5316f5-6078-469b-950c-9da893cd62c9 diff --git a/src/Envelope.cpp b/src/Envelope.cpp index 3f2b400b7..77b709498 100644 --- a/src/Envelope.cpp +++ b/src/Envelope.cpp @@ -70,14 +70,6 @@ Envelope::~Envelope() WX_CLEAR_ARRAY(mEnv); } -// TODO: Move Getters/Setters to Envelope.h and -// name them consistently Get/Set - -void Envelope::SetInterpolateDB(bool db) -{ - mDB = db; -} - void Envelope::Mirror(bool mirror) { mMirror = mirror; @@ -1068,13 +1060,12 @@ void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const /// @return value there, or its (safe) log10. double Envelope::GetInterpolationStartValueAtPoint( int iPoint ) const { - double v = mEnv[ iPoint ]->val; + //TODO-MB: make minimum value adjustable and apply this consistently + double v = std::max(1.0e-7, mEnv[ iPoint ]->val); if( !mDB ) return v; - // Special case for the log of zero - if (v <= 0.0) - return -7.0; // This corresponds to -140 dB - return log10( v ); + else + return log10(v); } void Envelope::GetValues(double *buffer, int bufferLen, @@ -1205,105 +1196,275 @@ double Envelope::Average( double t0, double t1 ) return Integral( t0, t1 ) / (t1 - t0); } +double Envelope::AverageOfInverse( double t0, double t1 ) +{ + if( t0 == t1 ) + return 1.0 / GetValue( t0 ); + else + return IntegralOfInverse( t0, t1 ) / (t1 - t0); +} + // // Integration and debugging functions // // The functions below are used by the TimeTrack and possibly for // other debugging. They do not affect normal amplitude envelopes // for waveforms, nor frequency envelopes for equalization. +// The 'Average' function also uses 'Integral'. // +// A few helper functions to make the code below more readable. +static double InterpolatePoints(double y1, double y2, double factor, bool logarithmic) +{ + if(logarithmic) + // you can use any base you want, it doesn't change the result + return exp(log(y1) * (1.0 - factor) + log(y2) * factor); + else + return y1 * (1.0 - factor) + y2 * factor; +} +static double IntegrateInterpolated(double y1, double y2, double time, bool logarithmic) +{ + // Calculates: integral(interpolate(y1, y2, x), x = 0 .. time) + // Integrating logarithmic interpolated segments is surprisingly simple. You can check this formula here: + // http://www.wolframalpha.com/input/?i=integrate+10%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29+from+0+to+T + // Again, the base you use for interpolation is irrelevant, the formula below should always use the natural + // logarithm (i.e. 'log' in C/C++). If the denominator is too small, it's better to use linear interpolation + // because the rounding errors would otherwise get too large. The threshold value is 1.0e-5 because at that + // point the rounding errors become larger than the difference between linear and logarithmic (I tested this in Octave). + if(logarithmic) + { + double l = log(y1 / y2); + if(fabs(l) < 1.0e-5) // fall back to linear interpolation + return (y1 + y2) * 0.5 * time; + return (y1 - y2) / l * time; + } + else + { + return (y1 + y2) * 0.5 * time; + } +} +static double IntegrateInverseInterpolated(double y1, double y2, double time, bool logarithmic) +{ + // Calculates: integral(1 / interpolate(y1, y2, x), x = 0 .. time) + // This one is a bit harder. Linear: + // http://www.wolframalpha.com/input/?i=integrate+1%2F%28y1*%28T-x%29%2FT%2By2*x%2FT%29+from+0+to+T + // Logarithmic: + // http://www.wolframalpha.com/input/?i=integrate+1%2F%2810%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29%29+from+0+to+T + // Here both cases need a special case for y1 == y2. The threshold is 1.0e5 again, this is still the + // best value in both cases. + double l = log(y1 / y2); + if(fabs(l) < 1.0e-5) // fall back to average + return 2.0 / (y1 + y2) * time; + if(logarithmic) + return (y1 - y2) / (l * y1 * y2) * time; + else + return l / (y1 - y2) * time; +} +static double SolveIntegrateInverseInterpolated(double y1, double y2, double time, double area, bool logarithmic) +{ + // Calculates: solve (integral(1 / interpolate(y1, y2, x), x = 0 .. res) = area) for res + // Don't try to derive these formulas by hand :). The threshold is 1.0e5 again. + double a = area / time, res; + if(logarithmic) + { + double l = log(y1 / y2); + if(fabs(l) < 1.0e-5) // fall back to average + res = a * (y1 + y2) * 0.5; + else if(1.0 + a * y1 * l <= 0.0) + res = 1.0; + else + res = log(1.0 + a * y1 * l) / l; + } + else + { + if(fabs(y2 - y1) < 1.0e-5) // fall back to average + res = a * (y1 + y2) * 0.5; + else + res = y1 * (exp(a * (y2 - y1)) - 1.0) / (y2 - y1); + } + return std::max(0.0, std::min(1.0, res)) * time; +} + // We should be able to write a very efficient memoizer for this // but make sure it gets reset when the envelope is changed. double Envelope::Integral( double t0, double t1 ) { - //printf( "\n\nIntegral: t0=%f, t1=%f\n", t0, t1 ); - double total=0; - - if( t0 == t1 ) - return 0; - if( t0 > t1 ) + if(t0 == t1) + return 0.0; + if(t0 > t1) { - printf( "Odd things happening in Integral!\n" ); - return mDefaultValue; + return -Integral(t1, t0); // this makes more sense than returning the default value } - unsigned int i = 0; - double lastT, lastVal; + unsigned int count = mEnv.Count(); + if(count == 0) // 'empty' envelope + return (t1 - t0) * mDefaultValue; - // t0 is one of three cases: - - // 0) in an 'empty' envelope - // 1) preceeding the first point - // 2) enclosed by points - // 3) following the last point - - if( mEnv.Count() < 1 ) // 0: 'empty' envelope + double total = 0.0, lastT, lastVal; + unsigned int i; // this is the next point to check + if(t0 < mEnv[0]->t) // t0 preceding the first point { - return (t1 - t0) * mDefaultValue; - } - else if( t0 < mEnv[0]->t ) // 1: preceeds the first - { - if( t1 <= mEnv[0]->t ){ + if(t1 <= mEnv[0]->t) return (t1 - t0) * mEnv[0]->val; - } - total += (mEnv[0]->t - t0) * mEnv[0]->val; + i = 1; lastT = mEnv[0]->t; lastVal = mEnv[0]->val; + total += (lastT - t0) * lastVal; } - else if( t0 >= mEnv[mEnv.Count()-1]->t ) // 3: follows the last + else if(t0 >= mEnv[count - 1]->t) // t0 following the last point { - return (t1 - t0) * mEnv[mEnv.Count()-1]->val; + return (t1 - t0) * mEnv[count - 1]->val; } - else - { // 2: bracketed + else // t0 enclosed by points + { // Skip any points that come before t0 using binary search - int lo,hi; - BinarySearchForTime( lo, hi, t0 ); - i = lo; - // i is now the point immediately before t0. - lastVal = ((mEnv[i]->val * (mEnv[i+1]->t - t0)) - + (mEnv[i+1]->val *(t0 - mEnv[i]->t))) - / (mEnv[i+1]->t - mEnv[i]->t); // value at t0 + int lo, hi; + BinarySearchForTime(lo, hi, t0); + lastVal = InterpolatePoints(mEnv[lo]->val, mEnv[hi]->val, (t0 - mEnv[lo]->t) / (mEnv[hi]->t - mEnv[lo]->t), mDB); lastT = t0; + i = hi; // the point immediately after t0. } - + // loop through the rest of the envelope points until we get to t1 while (1) { - - if(i >= mEnv.Count()-1) + if(i >= count) // the requested range extends beyond the last point { - // the requested range extends beyond last point return total + (t1 - lastT) * lastVal; } - else if (mEnv[i+1]->t >= t1) + else if(mEnv[i]->t >= t1) // this point follows the end of the range { - // last,i+1 bracket t1 - double thisVal = ((mEnv[i]->val * (mEnv[i+1]->t - t1)) - + (mEnv[i+1]->val *(t1 - mEnv[i]->t))) - / (mEnv[i+1]->t - mEnv[i]->t); - - return total + (t1 - lastT) * (thisVal + lastVal) / 2; + double thisVal = InterpolatePoints(mEnv[i - 1]->val, mEnv[i]->val, (t1 - mEnv[i - 1]->t) / (mEnv[i]->t - mEnv[i - 1]->t), mDB); + return total + IntegrateInterpolated(lastVal, thisVal, t1 - lastT, mDB); } - else + else // this point preceeds the end of the range { - // t1 still follows last,i+1 - total += (mEnv[i+1]->t - lastT) * (mEnv[i+1]->val + lastVal) / 2; - lastT = mEnv[i+1]->t; - lastVal = mEnv[i+1]->val; + total += IntegrateInterpolated(lastVal, mEnv[i]->val, mEnv[i]->t - lastT, mDB); + lastT = mEnv[i]->t; + lastVal = mEnv[i]->val; i++; } } } -// This one scales the y-axis before integrating. -// To re-scale [0,1] to [minY,maxY] we use the mapping y -> minY + (maxY - minY)y -// So we want to find the integral of (minY + (maxY - minY)f(t)), where f is our envelope. -// But that's just (t1 - t0)minY + (maxY - minY)Integral( t0, t1 ). -double Envelope::Integral( double t0, double t1, double minY, double maxY ) +double Envelope::IntegralOfInverse( double t0, double t1 ) { - return ((t1 - t0) * minY) + ((maxY - minY) * Integral( t0, t1 )); + if(t0 == t1) + return 0.0; + if(t0 > t1) + { + return -IntegralOfInverse(t1, t0); // this makes more sense than returning the default value + } + + unsigned int count = mEnv.Count(); + if(count == 0) // 'empty' envelope + return (t1 - t0) / mDefaultValue; + + double total = 0.0, lastT, lastVal; + unsigned int i; // this is the next point to check + if(t0 < mEnv[0]->t) // t0 preceding the first point + { + if(t1 <= mEnv[0]->t) + return (t1 - t0) / mEnv[0]->val; + i = 1; + lastT = mEnv[0]->t; + lastVal = mEnv[0]->val; + total += (lastT - t0) / lastVal; + } + else if(t0 >= mEnv[count - 1]->t) // t0 following the last point + { + return (t1 - t0) / mEnv[count - 1]->val; + } + else // t0 enclosed by points + { + // Skip any points that come before t0 using binary search + int lo, hi; + BinarySearchForTime(lo, hi, t0); + lastVal = InterpolatePoints(mEnv[lo]->val, mEnv[hi]->val, (t0 - mEnv[lo]->t) / (mEnv[hi]->t - mEnv[lo]->t), mDB); + lastT = t0; + 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 + { + return total + (t1 - lastT) / lastVal; + } + else if(mEnv[i]->t >= t1) // this point follows the end of the range + { + double thisVal = InterpolatePoints(mEnv[i - 1]->val, mEnv[i]->val, (t1 - mEnv[i - 1]->t) / (mEnv[i]->t - mEnv[i - 1]->t), mDB); + return total + IntegrateInverseInterpolated(lastVal, thisVal, t1 - lastT, mDB); + } + else // this point preceeds the end of the range + { + total += IntegrateInverseInterpolated(lastVal, mEnv[i]->val, mEnv[i]->t - lastT, mDB); + lastT = mEnv[i]->t; + lastVal = mEnv[i]->val; + i++; + } + } +} + +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 + if(t0 < mEnv[0]->t) // t0 preceding the first point + { + i = 1; + lastT = mEnv[0]->t; + lastVal = mEnv[0]->val; + double added = (lastT - t0) / lastVal; + if(added >= area) + return t0 + area * mEnv[0]->val; + area -= added; + } + else if(t0 >= mEnv[count - 1]->t) // t0 following the last point + { + return t0 + area * mEnv[count - 1]->val; + } + else // t0 enclosed by points + { + // Skip any points that come before t0 using binary search + int lo, hi; + BinarySearchForTime(lo, hi, t0); + lastVal = InterpolatePoints(mEnv[lo]->val, mEnv[hi]->val, (t0 - mEnv[lo]->t) / (mEnv[hi]->t - mEnv[lo]->t), mDB); + lastT = t0; + 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 + { + return lastT + area * lastVal; + } + else + { + double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->val, mEnv[i]->t - lastT, mDB); + if(added >= area) + return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->val, mEnv[i]->t - lastT, area, mDB); + area -= added; + lastT = mEnv[i]->t; + lastVal = mEnv[i]->val; + i++; + } + } } void Envelope::print() @@ -1383,14 +1544,3 @@ void Envelope::testMe() checkResult( 19, NextPointAfter( 5 ), 10 ); } -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 35b619bd-685f-45ee-89f0-bea14839de88 - diff --git a/src/Envelope.h b/src/Envelope.h index e16eeb092..797ba7996 100644 --- a/src/Envelope.h +++ b/src/Envelope.h @@ -80,7 +80,8 @@ class Envelope : public XMLTagHandler { virtual ~ Envelope(); - void SetInterpolateDB(bool db); + bool GetInterpolateDB() { return mDB; } + void SetInterpolateDB(bool db) { mDB = db; } void Mirror(bool mirror); void Flatten(double value); @@ -129,7 +130,7 @@ class Envelope : public XMLTagHandler { // Control void SetOffset(double newOffset); void SetTrackLen(double trackLen); - + // Accessors /** \brief Get envelope value at time t */ double GetValue(double t) const; @@ -146,8 +147,10 @@ class Envelope : public XMLTagHandler { double NextPointAfter(double t); double Average( double t0, double t1 ); + double AverageOfInverse( double t0, double t1 ); double Integral( double t0, double t1 ); - double Integral( double t0, double t1, double minY, double maxY ); + double IntegralOfInverse( double t0, double t1 ); + double SolveIntegralOfInverse( double t0, double area); void print(); void testMe(); @@ -231,15 +234,3 @@ private: #endif - -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: ab815f84-1f8c-4560-a165-271d3bae377e - diff --git a/src/Mix.cpp b/src/Mix.cpp index 02d91950d..39f45f7ab 100644 --- a/src/Mix.cpp +++ b/src/Mix.cpp @@ -42,6 +42,7 @@ #include "Resample.h" #include "float_cast.h" +//TODO-MB: wouldn't it make more sense to delete the time track after 'mix and render'? bool MixAndRender(TrackList *tracks, TrackFactory *trackFactory, double rate, sampleFormat format, double startTime, double endTime, @@ -244,7 +245,7 @@ Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks, mTimeTrack = timeTrack; mT0 = startTime; mT1 = stopTime; - mT = startTime; + mTime = startTime; mNumChannels = numOutChannels; mBufferSize = outBufferSize; mInterleaved = outInterleaved; @@ -284,11 +285,10 @@ Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks, mResample = new Resample*[mNumInputTracks]; for(i=0; iGetRate()); - double lowFactor = factor, highFactor = factor; if (timeTrack) { - highFactor /= timeTrack->GetRangeLower() / 100.0; - lowFactor /= timeTrack->GetRangeUpper() / 100.0; - mResample[i] = new VarRateResample(highQuality, lowFactor, highFactor); + mResample[i] = new VarRateResample(highQuality, + factor / timeTrack->GetRangeUpper(), + factor / timeTrack->GetRangeLower()); } else { mResample[i] = new ConstRateResample(highQuality, factor); } @@ -437,26 +437,23 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track, } } - double factor = initialWarp; - if (mTimeTrack) - { - Envelope* pEnvelope = mTimeTrack->GetEnvelope(); - if (pEnvelope) - { - double warpFactor = pEnvelope->GetValue(t); - warpFactor = (mTimeTrack->GetRangeLower() * (1.0 - warpFactor) + - warpFactor * mTimeTrack->GetRangeUpper()) / 100.0; - - factor /= warpFactor; - } - } - sampleCount thisProcessLen = mProcessLen; bool last = (*queueLen < mProcessLen); if (last) { thisProcessLen = *queueLen; } + double factor = initialWarp; + if (mTimeTrack) + { + //TODO-MB: The end time is wrong when the resampler doesn't use all input samples, + // 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); + } + int input_used; int outgen = pResample->Process(factor, &queue[*queueStart], @@ -540,8 +537,10 @@ sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track, sampleCount Mixer::Process(sampleCount maxToProcess) { - if (mT >= mT1) - return 0; + // MB: this is wrong! mT represented warped time, and mTime is too inaccurate to use + // it here. It's also unnecessary I think. + //if (mT >= mT1) + // return 0; int i, j; sampleCount out; @@ -589,12 +588,18 @@ sampleCount Mixer::Process(sampleCount maxToProcess) if (out > maxOut) maxOut = out; + + double t = (double)mSamplePos[i] / (double)track->GetRate(); + if(t > mTime) + mTime = std::min(t, mT1); + } out = mInterleaved ? maxOut * mNumChannels : maxOut; for(int c=0; cTimeToLongSamples(mT0); @@ -635,14 +640,14 @@ void Mixer::Reposition(double t) { int i; - mT = t; - if( mT < mT0 ) - mT = mT0; - if( mT > mT1 ) - mT = mT1; + mTime = t; + if( mTime < mT0 ) + mTime = mT0; + if( mTime > mT1 ) + mTime = mT1; for(i=0; iTimeToLongSamples(mT); + mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mTime); mQueueStart[i] = 0; mQueueLen[i] = 0; } @@ -734,14 +739,3 @@ MixerSpec& MixerSpec::operator=( const MixerSpec &mixerSpec ) return *this; } -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: d4e10e74-cdf9-46ac-b309-91b115d2a78f - diff --git a/src/Mix.h b/src/Mix.h index 72bfe77ca..bb66c3b1a 100644 --- a/src/Mix.h +++ b/src/Mix.h @@ -104,7 +104,8 @@ class AUDACITY_DLL_API Mixer { /// Process() is called. void Reposition(double t); - /// Current time in seconds + /// Current time in seconds (unwarped, i.e. always between startTime and stopTime) + /// This value is not accurate, it's useful for progress bars and indicators, but nothing else. double MixGetCurrentTime(); /// Retrieve the main buffer or the interleaved buffer @@ -133,9 +134,11 @@ class AUDACITY_DLL_API Mixer { bool mApplyTrackGains; float *mGains; double *mEnvValues; - double mT; // Current time double mT0; // Start time double mT1; // Stop time (none if mT0==mT1) + double mTime; // Current time (renamed from mT to mTime for consistency with AudioIO - mT represented warped time there) +// double mWarpedTime; // current time after warping, starting at zero (unlike mTime) +// double mWarpedLength; // total length after warping Resample **mResample; float **mSampleQueue; int *mQueueStart; @@ -160,14 +163,3 @@ class AUDACITY_DLL_API Mixer { #endif -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 9d4211b3-0241-4689-bc53-3e9460a04cb6 - diff --git a/src/Resample.cpp b/src/Resample.cpp index 428dded46..1fd0baa11 100644 --- a/src/Resample.cpp +++ b/src/Resample.cpp @@ -168,6 +168,12 @@ : Resample(useBestMethod) { mHandle = resample_open(mMethod, dMinFactor, dMaxFactor); + if(mHandle == NULL) { + fprintf(stderr, "libresample doesn't support range %f .. %f.\n", dMinFactor, dMaxFactor); + // FIX-ME: Audacity will hang after this if branch. + mHandle = NULL; + return; + } } VarRateResample::~VarRateResample() @@ -240,6 +246,8 @@ int err; SRC_STATE *state = src_new(mMethod, 1, &err); mHandle = (void *)state; + mShouldReset = false; + mSamplesLeft = 0; } VarRateResample::~VarRateResample() @@ -299,6 +307,15 @@ src_set_ratio((SRC_STATE *)mHandle, factor); SRC_DATA data; + + if(mShouldReset) { + if(inBufferLen > mSamplesLeft) { + mShouldReset = false; + src_reset((SRC_STATE *)mHandle); + } else { + mSamplesLeft -= inBufferLen; + } + } data.data_in = inBuffer; data.data_out = outBuffer; @@ -314,6 +331,11 @@ wxFprintf(stderr, _("Libsamplerate error: %d\n"), err); return 0; } + + if(lastFlag) { + mShouldReset = true; + mSamplesLeft = inBufferLen - (int)data.input_frames_used; + } *inBufferUsed = (int)data.input_frames_used; return (int)data.output_frames_gen; diff --git a/src/Resample.h b/src/Resample.h index d0b560ba3..ac00e2f7d 100644 --- a/src/Resample.h +++ b/src/Resample.h @@ -115,6 +115,10 @@ class Resample protected: int mMethod; // resampler-specific enum for resampling method void* mHandle; // constant-rate or variable-rate resampler (XOR per instance) +#if USE_LIBSAMPLERATE + bool mShouldReset; // whether the resampler should be reset because lastFlag has been set previously + int mSamplesLeft; // number of samples left before a reset is needed +#endif }; class ConstRateResample : public Resample diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index c784b3602..dbb8c1155 100644 --- a/src/TimeTrack.cpp +++ b/src/TimeTrack.cpp @@ -31,16 +31,19 @@ TimeTrack *TrackFactory::NewTimeTrack() TimeTrack::TimeTrack(DirManager *projDirManager): Track(projDirManager) { - mHeight = 50; + mHeight = 100; - mRangeLower = 90; - mRangeUpper = 110; + mRangeLower = 0.9; + mRangeUpper = 1.1; + mDisplayLog = false; mEnvelope = new Envelope(); mEnvelope->SetTrackLen(1000000000.0); - mEnvelope->SetInterpolateDB(false); - mEnvelope->Flatten(0.5); + mEnvelope->SetInterpolateDB(true); + mEnvelope->Flatten(1.0); mEnvelope->Mirror(false); + mEnvelope->SetOffset(0); + SetDefaultName(_("Time Track")); SetName(GetDefaultName()); @@ -57,15 +60,10 @@ TimeTrack::TimeTrack(TimeTrack &orig): { Init(orig); - mHeight = 50; - - mRangeLower = 90; - mRangeUpper = 110; - mEnvelope = new Envelope(); mEnvelope->SetTrackLen(1000000000.0); - mEnvelope->SetInterpolateDB(false); - mEnvelope->Flatten(0.5); + SetInterpolateLog(orig.GetInterpolateLog()); // this calls Envelope::SetInterpolateDB + mEnvelope->Flatten(1.0); mEnvelope->Mirror(false); mEnvelope->Paste(0.0, orig.mEnvelope); mEnvelope->SetOffset(0); @@ -84,6 +82,9 @@ void TimeTrack::Init(const TimeTrack &orig) Track::Init(orig); SetDefaultName(orig.GetDefaultName()); SetName(orig.GetName()); + SetRangeLower(orig.GetRangeLower()); + SetRangeUpper(orig.GetRangeUpper()); + SetDisplayLog(orig.GetDisplayLog()); } TimeTrack::~TimeTrack() @@ -99,35 +100,35 @@ Track *TimeTrack::Duplicate() return new TimeTrack(*this); } -// Our envelope represents the playback speed, which is the rate of change of -// playback position. We want to find the playback position at time t, so -// we have to integrate the playback speed. -double TimeTrack::warp( double t ) +bool TimeTrack::GetInterpolateLog() const { - double result = GetEnvelope()->Integral( 0.0, t, - GetRangeLower()/100.0, - GetRangeUpper()/100.0 ); - //printf( "Warping %.2f to %.2f\n", t, result ); - return result; + return mEnvelope->GetInterpolateDB(); } -//Compute the integral warp factor between two non-warped time points +void TimeTrack::SetInterpolateLog(bool interpolateLog) { + mEnvelope->SetInterpolateDB(interpolateLog); +} + +//Compute the (average) warp factor between two non-warped time points double TimeTrack::ComputeWarpFactor(double t0, double t1) { - double factor; - factor = GetEnvelope()->Average(t0, t1); - factor = (GetRangeLower() * - (1 - factor) + - factor * - GetRangeUpper()) / - 100.0; - return factor; + return GetEnvelope()->AverageOfInverse(t0, t1); +} + +double TimeTrack::ComputeWarpedLength(double t0, double t1) +{ + return GetEnvelope()->IntegralOfInverse(t0, t1); +} + +double TimeTrack::SolveWarpedLength(double t0, double length) +{ + return GetEnvelope()->SolveIntegralOfInverse(t0, length); } bool TimeTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { if (!wxStrcmp(tag, wxT("timetrack"))) { - double dblValue; + mRescaleXMLValues = true; // will be set to false if upper/lower is found long nValue; while(*attrs) { const wxChar *attr = *attrs++; @@ -137,29 +138,36 @@ bool TimeTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs) break; const wxString strValue = value; - if (!wxStrcmp(attr, wxT("offset"))) - { - if (!XMLValueChecker::IsGoodString(strValue) || - !Internat::CompatibleToDouble(strValue, &dblValue)) - return false; - mOffset = dblValue; - mEnvelope->SetOffset(mOffset); - } - else if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue)) + if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue)) mName = strValue; - else if (!wxStrcmp(attr, wxT("channel"))) - { - if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) || - !XMLValueChecker::IsValidChannel(nValue)) - return false; - mChannel = nValue; - } else if (!wxStrcmp(attr, wxT("height")) && XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) mHeight = nValue; else if (!wxStrcmp(attr, wxT("minimized")) && XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) mMinimized = (nValue != 0); + else if (!wxStrcmp(attr, wxT("rangelower"))) + { + mRangeLower = Internat::CompatibleToDouble(value); + mRescaleXMLValues = false; + } + else if (!wxStrcmp(attr, wxT("rangeupper"))) + { + mRangeUpper = Internat::CompatibleToDouble(value); + mRescaleXMLValues = false; //TODO-MB: figure out how to rescale after loading + } + else if (!wxStrcmp(attr, wxT("displaylog")) && + XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) + { + SetDisplayLog(nValue != 0); + //TODO-MB: This causes a graphical glitch, TrackPanel should probably be Refresh()ed after loading. + // I don't know where to do this though. + } + else if (!wxStrcmp(attr, wxT("interpolatelog")) && + XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) + { + SetInterpolateLog(nValue != 0); + } } // while return true; @@ -181,10 +189,14 @@ void TimeTrack::WriteXML(XMLWriter &xmlFile) xmlFile.StartTag(wxT("timetrack")); xmlFile.WriteAttr(wxT("name"), mName); - xmlFile.WriteAttr(wxT("channel"), mChannel); - xmlFile.WriteAttr(wxT("offset"), mOffset, 8); - xmlFile.WriteAttr(wxT("height"), this->GetActualHeight()); - xmlFile.WriteAttr(wxT("minimized"), this->GetMinimized()); + //xmlFile.WriteAttr(wxT("channel"), mChannel); + //xmlFile.WriteAttr(wxT("offset"), mOffset, 8); + xmlFile.WriteAttr(wxT("height"), GetActualHeight()); + xmlFile.WriteAttr(wxT("minimized"), GetMinimized()); + xmlFile.WriteAttr(wxT("rangelower"), mRangeLower, 12); + xmlFile.WriteAttr(wxT("rangeupper"), mRangeUpper, 12); + xmlFile.WriteAttr(wxT("displaylog"), GetDisplayLog()); + xmlFile.WriteAttr(wxT("interpolatelog"), GetInterpolateLog()); mEnvelope->WriteXML(xmlFile); @@ -222,31 +234,26 @@ void TimeTrack::Draw(wxDC & dc, const wxRect & r, double h, double pps) // // LL: It's because the ruler only Invalidate()s when the new value is different // than the current value. - mRuler->SetFlip(GetHeight() > 75 ? true : true); + mRuler->SetFlip(GetHeight() > 75 ? true : true); // MB: so why don't we just call Invalidate()? :) mRuler->Draw(dc, this); - int *heights = new int[mid.width]; double *envValues = new double[mid.width]; GetEnvelope()->GetValues(envValues, mid.width, t0, tstep); - double t = t0; - int x; - for (x = 0; x < mid.width; x++) - { - heights[x] = (int)(mid.height * (1 - envValues[x])); - t += tstep; - } - dc.SetPen(AColor::envelopePen); - for (x = 0; x < mid.width; x++) + double logLower = log(std::max(1.0e-7, mRangeLower)), logUpper = log(std::max(1.0e-7, mRangeUpper)); + for (int x = 0; x < mid.width; x++) { - int thisy = r.y + heights[x]; - AColor::Line(dc, mid.x + x, thisy, mid.x + x, thisy+3); + double y; + if(mDisplayLog) + y = (double)mid.height * (logUpper - log(envValues[x])) / (logUpper - logLower); + else + y = (double)mid.height * (mRangeUpper - envValues[x]) / (mRangeUpper - mRangeLower); + int thisy = r.y + (int)y; + AColor::Line(dc, mid.x + x, thisy - 1, mid.x + x, thisy+2); } - if (heights) - delete[]heights; if (envValues) delete[]envValues; } @@ -255,11 +262,25 @@ void TimeTrack::testMe() { GetEnvelope()->SetDefaultValue(0.5); GetEnvelope()->Flatten(0.0); - GetEnvelope()->Insert( 0.0, 0.0 ); - GetEnvelope()->Insert( 5.0, 1.0 ); - GetEnvelope()->Insert( 10.0, 0.0 ); + GetEnvelope()->Insert( 0.0, 0.2 ); + GetEnvelope()->Insert( 5.0 - 0.001, 0.2 ); + GetEnvelope()->Insert( 5.0 + 0.001, 1.3 ); + GetEnvelope()->Insert( 10.0, 1.3 ); + + double value1 = GetEnvelope()->Integral(2.0, 13.0); + double expected1 = (5.0 - 2.0) * 0.2 + (13.0 - 5.0) * 1.3; + double value2 = GetEnvelope()->IntegralOfInverse(2.0, 13.0); + double expected2 = (5.0 - 2.0) / 0.2 + (13.0 - 5.0) / 1.3; + if( fabs(value1 - expected1) > 0.01 ) + { + printf( "TimeTrack: Integral failed! expected %f got %f\n", expected1, value1); + } + if( fabs(value2 - expected2) > 0.01 ) + { + printf( "TimeTrack: IntegralOfInverse failed! expected %f got %f\n", expected2, value2); + } - double reqt0 = 10.0 - .1; + /*double reqt0 = 10.0 - .1; double reqt1 = 10.0 + .1; double t0 = warp( reqt0 ); double t1 = warp( reqt1 ); @@ -268,17 +289,6 @@ void TimeTrack::testMe() printf( "TimeTrack: Warping reverses an interval! [%.2f,%.2f] -> [%.2f,%.2f]\n", reqt0, reqt1, t0, t1 ); - } + }*/ } -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 8622daf1-c09a-4dcd-8b71-615d194343c7 - diff --git a/src/TimeTrack.h b/src/TimeTrack.h index ffa8127b3..43abad02d 100644 --- a/src/TimeTrack.h +++ b/src/TimeTrack.h @@ -58,26 +58,47 @@ class TimeTrack: public Track { Envelope *GetEnvelope() { return mEnvelope; } - //Compute the integral warp factor between two non-warped time points + //Note: The meaning of this function has changed (December 2012) + //Previously this function did something that was close to the opposite (but not entirely accurate). + /** @brief Compute the integral warp factor between two non-warped time points + * + * Calculate the relative length increase of the chosen segment from the original sound. + * So if this time track has a low value (i.e. makes the sound slower), the new warped + * sound will be *longer* than the original sound, so the return value of this function + * is larger. + * @param t0 The starting time to calculate from + * @param t1 The ending time to calculate to + * @return The relative length increase of the chosen segment from the original sound. + */ double ComputeWarpFactor(double t0, double t1); + double ComputeWarpedLength(double t0, double t1); + double SolveWarpedLength(double t0, double length); // Get/Set the speed-warping range, as percentage of original speed (e.g. 90%-110%) - long GetRangeLower() { return mRangeLower ? mRangeLower : 1; } - long GetRangeUpper() { return mRangeUpper ? mRangeUpper : 1; } + //TODO-MB: What's a sensible minimum value? Also, TrackPanel already forces a much + // higher minimum value (13%), so what's the point of adding another one here? + // Besides, Envelope should probably handle this, not TimeTrack (or TrackPanel). + double GetRangeLower() const { return mRangeLower; } + double GetRangeUpper() const { return mRangeUpper; } - void SetRangeLower(long lower) { mRangeLower = lower; } - void SetRangeUpper(long upper) { mRangeUpper = upper; } + void SetRangeLower(double lower) { mRangeLower = lower; } + void SetRangeUpper(double upper) { mRangeUpper = upper; } - double warp( double t ); + bool GetDisplayLog() const { return mDisplayLog; } + void SetDisplayLog(bool displayLog) { mDisplayLog = displayLog; } + bool GetInterpolateLog() const; + void SetInterpolateLog(bool interpolateLog); void testMe(); private: Envelope *mEnvelope; Ruler *mRuler; - long mRangeLower; - long mRangeUpper; + double mRangeLower; + double mRangeUpper; + bool mDisplayLog; + bool mRescaleXMLValues; // needed for backward-compatibility with older project files void Init(const TimeTrack &orig); virtual Track *Duplicate(); @@ -91,14 +112,3 @@ class TimeTrack: public Track { #endif // __AUDACITY_TIMETRACK__ -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 58e4cd09-07ee-47d0-bcb9-a37ddcac8483 - diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index d81b546d6..54503da5f 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -446,7 +446,7 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) // Label and Time tracks do not have a vruler // But give it a beveled area - if (kind == Track::Label || kind == Track::Time) { + if (kind == Track::Label) { wxRect bev = r; bev.Inflate(-1, -1); bev.width += 1; @@ -455,6 +455,29 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) return; } + // Time tracks + if (kind == Track::Time) { + wxRect bev = r; + bev.Inflate(-1, -1); + bev.width += 1; + AColor::BevelTrackInfo(*dc, true, bev); + + // Right align the ruler + wxRect rr = r; + rr.width--; + if (t->vrulerSize.GetWidth() < r.GetWidth()) { + int adj = rr.GetWidth() - t->vrulerSize.GetWidth(); + rr.x += adj; + rr.width -= adj; + } + + UpdateVRuler(t, rr); + + vruler->Draw(*dc); + + return; + } + // All waves have a ruler in the info panel // The ruler needs a bevelled surround. if (kind == Track::Wave) { @@ -591,6 +614,22 @@ void TrackArtist::UpdateVRuler(Track *t, wxRect & r) return; } + // Time tracks + if (t->GetKind() == Track::Time) { + TimeTrack *tt = (TimeTrack *)t; + float min, max; + min = tt->GetRangeLower() * 100.0; + max = tt->GetRangeUpper() * 100.0; + + vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1); + vruler->SetOrientation(wxVERTICAL); + vruler->SetRange(max, min); + vruler->SetFormat((tt->GetDisplayLog()) ? Ruler::RealLogFormat : Ruler::RealFormat); + vruler->SetUnits(wxT("")); + vruler->SetLabelEdges(false); + vruler->SetLog(tt->GetDisplayLog()); + } + // All waves have a ruler in the info panel // The ruler needs a bevelled surround. if (t->GetKind() == Track::Wave) { @@ -2808,8 +2847,15 @@ void TrackArtist::DrawTimeTrack(TimeTrack *track, track->Draw(dc, r, viewInfo->h, viewInfo->zoom); wxRect envRect = r; envRect.height -= 2; + double lower = track->GetRangeLower(), upper = track->GetRangeUpper(); + if(track->GetDisplayLog()) { + // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale + double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); + lower = 20.0 * log10(std::max(1.0e-7, lower)) / dBRange + 1.0; + upper = 20.0 * log10(std::max(1.0e-7, upper)) / dBRange + 1.0; + } track->GetEnvelope()->DrawPoints(dc, envRect, viewInfo->h, viewInfo->zoom, - false,0.0,1.0); + track->GetDisplayLog(), lower, upper); } void TrackArtist::UpdatePrefs() diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index bd7c777d8..6445d6941 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -318,6 +318,10 @@ enum { OnCutSelectedTextID, OnCopySelectedTextID, OnPasteSelectedTextID, + + OnTimeTrackLinID, + OnTimeTrackLogID, + OnTimeTrackLogIntID, }; BEGIN_EVENT_TABLE(TrackPanel, wxWindow) @@ -351,6 +355,10 @@ BEGIN_EVENT_TABLE(TrackPanel, wxWindow) EVT_MENU(OnCutSelectedTextID, TrackPanel::OnCutSelectedText) EVT_MENU(OnCopySelectedTextID, TrackPanel::OnCopySelectedText) EVT_MENU(OnPasteSelectedTextID, TrackPanel::OnPasteSelectedText) + + EVT_MENU(OnTimeTrackLinID, TrackPanel::OnTimeTrackLin) + EVT_MENU(OnTimeTrackLogID, TrackPanel::OnTimeTrackLog) + EVT_MENU(OnTimeTrackLogIntID, TrackPanel::OnTimeTrackLogInt) END_EVENT_TABLE() /// Makes a cursor from an XPM, uses CursorId as a fallback. @@ -684,7 +692,11 @@ void TrackPanel::BuildMenus(void) mTimeTrackMenu->Append(OnMoveUpID, _("Move Track U&p")); mTimeTrackMenu->Append(OnMoveDownID, _("Move Track &Down")); mTimeTrackMenu->AppendSeparator(); + mTimeTrackMenu->Append(OnTimeTrackLinID, _("&Linear")); + mTimeTrackMenu->Append(OnTimeTrackLogID, _("L&ogarithmic")); + mTimeTrackMenu->AppendSeparator(); mTimeTrackMenu->Append(OnSetTimeTrackRangeID, _("Set Ra&nge...")); + mTimeTrackMenu->AppendCheckItem(OnTimeTrackLogIntID, _("Logarithmic &Interpolation")); mLabelTrackInfoMenu = new wxMenu(); mLabelTrackInfoMenu->Append(OnCutSelectedTextID, _("Cut")); @@ -1231,17 +1243,8 @@ void TrackPanel::DoDrawCursor(wxDC & dc) wxCoord top = y + kTopInset; wxCoord bottom = y + t->GetHeight() - kTopInset; - if( t->GetKind() == Track::Time ) - { - TimeTrack *tt = (TimeTrack *) t; - double t0 = tt->warp( mLastCursor - mViewInfo->h ); - int warpedX = GetLeftOffset() + int ( t0 * mViewInfo->zoom ); - AColor::Line( dc, warpedX, top, warpedX, bottom ); - } - else - { - AColor::Line( dc, x, top, x, bottom ); // <-- The whole point of this routine. - } + // MB: warp() is not needed here as far as I know, in fact it creates a bug. Removing it fixes that. + AColor::Line( dc, x, top, x, bottom ); // <-- The whole point of this routine. } } @@ -2456,11 +2459,18 @@ void TrackPanel::ForwardEventToTimeTrackEnvelope(wxMouseEvent & event) wxRect envRect = mCapturedRect; envRect.y++; envRect.height -= 2; + double lower = ptimetrack->GetRangeLower(), upper = ptimetrack->GetRangeUpper(); + if(ptimetrack->GetDisplayLog()) { + // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale + double dBRange = gPrefs->Read(wxT("/GUI/EnvdBRange"), ENV_DB_RANGE); + lower = 20.0 * log10(std::max(1.0e-7, lower)) / dBRange + 1.0; + upper = 20.0 * log10(std::max(1.0e-7, upper)) / dBRange + 1.0; + } bool needUpdate = pspeedenvelope->MouseEvent( event, envRect, mViewInfo->h, mViewInfo->zoom, - false,0.,1.); + ptimetrack->GetDisplayLog(), lower, upper); if (needUpdate) { RefreshTrack(mCapturedTrack); } @@ -3245,6 +3255,7 @@ void TrackPanel::HandleVZoom(wxMouseEvent & event) else if (event.ButtonUp()) { HandleVZoomButtonUp( event ); } + //TODO-MB: add timetrack zooming here! } /// VZoom click @@ -6751,8 +6762,15 @@ void TrackPanel::OnTrackMenu(Track *t) Track *next = mTracks->GetNext(t); wxMenu *theMenu = NULL; - if (t->GetKind() == Track::Time) + if (t->GetKind() == Track::Time) { theMenu = mTimeTrackMenu; + + TimeTrack *tt = (TimeTrack*) t; + + theMenu->Enable(OnTimeTrackLinID, tt->GetDisplayLog()); + theMenu->Enable(OnTimeTrackLogID, !tt->GetDisplayLog()); + theMenu->Check(OnTimeTrackLogIntID, tt->GetInterpolateLog()); + } if (t->GetKind() == Track::Wave) { theMenu = mWaveTrackMenu; @@ -7373,8 +7391,8 @@ void TrackPanel::OnSetTimeTrackRange(wxCommandEvent & /*event*/) TimeTrack *t = (TimeTrack*)mPopupMenuTarget; if (t) { - long lower = t->GetRangeLower(); - long upper = t->GetRangeUpper(); + long lower = (long) (t->GetRangeLower() * 100.0 + 0.5); + long upper = (long) (t->GetRangeUpper() * 100.0 + 0.5); lower = wxGetNumberFromUser(_("Change lower speed limit (%) to:"), _("Lower speed limit"), @@ -7390,9 +7408,10 @@ void TrackPanel::OnSetTimeTrackRange(wxCommandEvent & /*event*/) lower+1, 1200); + //TODO-MB: what's the meaning of these values? if( lower >= 13 && upper <= 1200 && lower < upper ) { - t->SetRangeLower(lower); - t->SetRangeUpper(upper); + t->SetRangeLower((double)lower / 100.0); + t->SetRangeUpper((double)upper / 100.0); MakeParentPushState(wxString::Format(_("Set range to '%d' - '%d'"), lower, upper), @@ -7405,6 +7424,37 @@ void TrackPanel::OnSetTimeTrackRange(wxCommandEvent & /*event*/) mPopupMenuTarget = NULL; } +void TrackPanel::OnTimeTrackLin(wxCommandEvent & /*event*/) +{ + TimeTrack *t = (TimeTrack*)mPopupMenuTarget; + t->SetDisplayLog(false); + UpdateVRuler(t); + MakeParentPushState(_("Set time track display to linear"), _("Set Display")); + Refresh(false); +} + +void TrackPanel::OnTimeTrackLog(wxCommandEvent & /*event*/) +{ + TimeTrack *t = (TimeTrack*)mPopupMenuTarget; + t->SetDisplayLog(true); + UpdateVRuler(t); + MakeParentPushState(_("Set time track display to logarithmic"), _("Set Display")); + Refresh(false); +} + +void TrackPanel::OnTimeTrackLogInt(wxCommandEvent & /*event*/) +{ + TimeTrack *t = (TimeTrack*)mPopupMenuTarget; + if(t->GetInterpolateLog()) { + t->SetInterpolateLog(false); + MakeParentPushState(_("Set time track interpolation to linear"), _("Set Interpolation")); + } else { + t->SetInterpolateLog(true); + MakeParentPushState(_("Set time track interpolation to logarithmic"), _("Set Interpolation")); + } + Refresh(false); +} + /// AS: Move a track up or down, depending. void TrackPanel::OnMoveTrack(wxCommandEvent & event) { diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 28d7c0aae..20f167e4f 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -428,6 +428,9 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel { virtual void OnChannelChange(wxCommandEvent &event); virtual void OnSetDisplay (wxCommandEvent &event); virtual void OnSetTimeTrackRange (wxCommandEvent &event); + virtual void OnTimeTrackLin(wxCommandEvent &event); + virtual void OnTimeTrackLog(wxCommandEvent &event); + virtual void OnTimeTrackLogInt(wxCommandEvent &event); virtual void SetMenuCheck( wxMenu & menu, int newId ); virtual void SetRate(Track *pTrack, double rate); @@ -721,13 +724,3 @@ protected: #endif -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 1f8c3d0e-849e-4f3c-95b5-9ead0789f999 diff --git a/src/export/Export.cpp b/src/export/Export.cpp index 4873bfb4f..1da152a4e 100644 --- a/src/export/Export.cpp +++ b/src/export/Export.cpp @@ -274,16 +274,10 @@ Mixer* ExportPlugin::CreateMixer(int numInputTracks, WaveTrack **inputTracks, double outRate, sampleFormat outFormat, bool highQuality, MixerSpec *mixerSpec) { - double warpedStopTime; - double warpFactor = 1.0; - if(timeTrack) - warpFactor = timeTrack->ComputeWarpFactor(startTime, stopTime); - - warpedStopTime = warpFactor >= 1.0 ? stopTime : (startTime + ((stopTime - startTime) / warpFactor)); - printf("warpfactor %f, stoptime %f, warpstoptime %f\n",warpFactor, stopTime, warpedStopTime); + // MB: the stop time should not be warped, this was a bug. return new Mixer(numInputTracks, inputTracks, timeTrack, - startTime, warpedStopTime, + startTime, stopTime, numOutChannels, outBufferSize, outInterleaved, outRate, outFormat, highQuality, mixerSpec); @@ -1213,14 +1207,3 @@ void ExportMixerDialog::OnCancel(wxCommandEvent &event) EndModal( wxID_CANCEL ); } -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: e6901653-9e2a-4a97-8ba8-377928b8e45a - diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp index 33ec97b7a..ab14ddf24 100644 --- a/src/toolbars/TranscriptionToolBar.cpp +++ b/src/toolbars/TranscriptionToolBar.cpp @@ -409,8 +409,9 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & event) } // Set the speed range - mTimeTrack->SetRangeUpper((long int)mPlaySpeed); - mTimeTrack->SetRangeLower((long int)mPlaySpeed); + //mTimeTrack->SetRangeUpper((double)mPlaySpeed / 100.0); + //mTimeTrack->SetRangeLower((double)mPlaySpeed / 100.0); + mTimeTrack->GetEnvelope()->Flatten((double)mPlaySpeed / 100.0); // Get the current play region double playRegionStart, playRegionEnd; @@ -890,16 +891,3 @@ void TranscriptionToolBar::AdjustPlaySpeed(float adj) OnSpeedSlider(e); } - - -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: ToDo - diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 2ea1268fb..b8288abc7 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -1045,47 +1045,51 @@ void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, lo double sg = UPP > 0.0? 1.0: -1.0; // Major ticks - double d = mMin - UPP/2; - double lastD = d; + double d, warpedD; + d = mMin - UPP/2; + if(timetrack) + warpedD = timetrack->ComputeWarpedLength(0.0, d); + else + warpedD = d; // using ints for majorint doesn't work, as // majorint will overflow and be negative at high zoom. - double majorInt = floor(sg * d / mMajor); + double majorInt = floor(sg * warpedD / mMajor); i = -1; while(i <= mLength) { - double warpfactor; - if( d>0 && timetrack != NULL ) - warpfactor = timetrack->ComputeWarpFactor( lastD, d ); - else - warpfactor = 1.0; - i++; - lastD = d; - d += UPP/warpfactor; + if(timetrack) + warpedD += timetrack->ComputeWarpedLength(d, d + UPP); + else + warpedD += UPP; + d += UPP; - if (floor(sg * d / mMajor) > majorInt) { - majorInt = floor(sg * d / mMajor); + if (floor(sg * warpedD / mMajor) > majorInt) { + majorInt = floor(sg * warpedD / mMajor); Tick(i, sg * majorInt * mMajor, true, false); } } - + // Minor ticks d = mMin - UPP/2; - lastD = d; - int minorInt = (int)floor(sg * d / mMinor); + if(timetrack) + warpedD = timetrack->ComputeWarpedLength(0.0, d); + else + warpedD = d; + // using ints for majorint doesn't work, as + // majorint will overflow and be negative at high zoom. + // MB: I assume the same applies to minorInt + double minorInt = floor(sg * warpedD / mMinor); i = -1; while(i <= mLength) { - double warpfactor; - if( d>0 && timetrack != NULL ) - warpfactor = timetrack->ComputeWarpFactor( lastD, d ); - else - warpfactor = 1.0; - i++; - lastD = d; - d += UPP/warpfactor; + if(timetrack) + warpedD += timetrack->ComputeWarpedLength(d, d + UPP); + else + warpedD += UPP; + d += UPP; - if ((int)floor(sg * d / mMinor) > minorInt) { - minorInt = (int)floor(sg * d / mMinor); + if (floor(sg * warpedD / mMinor) > minorInt) { + minorInt = floor(sg * warpedD / mMinor); Tick(i, sg * minorInt * mMinor, false, true); } } @@ -2036,14 +2040,3 @@ void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height) ruler.GetMaxSize(width, height); } -// Indentation settings for Vim and Emacs and unique identifier for Arch, a -// version control system. Please do not modify past this point. -// -// Local Variables: -// c-basic-offset: 3 -// indent-tabs-mode: nil -// End: -// -// vim: et sts=3 sw=3 -// arch-tag: 126e06c2-f0c8-490f-bdd6-12581013f13f -