296 lines
8.2 KiB
C++
296 lines
8.2 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
PlaybackSchedule.cpp
|
|
|
|
Paul Licameli split from AudioIOBase.cpp
|
|
|
|
**********************************************************************/
|
|
|
|
#include "PlaybackSchedule.h"
|
|
|
|
#include "AudioIOBase.h"
|
|
#include "Envelope.h"
|
|
|
|
void PlaybackSchedule::Init(
|
|
const double t0, const double t1,
|
|
const AudioIOStartStreamOptions &options,
|
|
const RecordingSchedule *pRecordingSchedule )
|
|
{
|
|
if ( pRecordingSchedule )
|
|
// It does not make sense to apply the time warp during overdub recording,
|
|
// which defeats the purpose of making the recording synchronized with
|
|
// the existing audio. (Unless we figured out the inverse warp of the
|
|
// captured samples in real time.)
|
|
// So just quietly ignore the time track.
|
|
mEnvelope = nullptr;
|
|
else
|
|
mEnvelope = options.envelope;
|
|
|
|
mT0 = t0;
|
|
if (pRecordingSchedule)
|
|
mT0 -= pRecordingSchedule->mPreRoll;
|
|
|
|
mT1 = t1;
|
|
if (pRecordingSchedule)
|
|
// adjust mT1 so that we don't give paComplete too soon to fill up the
|
|
// desired length of recording
|
|
mT1 -= pRecordingSchedule->mLatencyCorrection;
|
|
|
|
// Main thread's initialization of mTime
|
|
SetTrackTime( mT0 );
|
|
|
|
mPlayMode = options.playLooped
|
|
? PlaybackSchedule::PLAY_LOOPED
|
|
: PlaybackSchedule::PLAY_STRAIGHT;
|
|
mCutPreviewGapStart = options.cutPreviewGapStart;
|
|
mCutPreviewGapLen = options.cutPreviewGapLen;
|
|
|
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
|
bool scrubbing = (options.pScrubbingOptions != nullptr);
|
|
|
|
// Scrubbing is not compatible with looping or recording or a time track!
|
|
if (scrubbing)
|
|
{
|
|
const auto &scrubOptions = *options.pScrubbingOptions;
|
|
if (pRecordingSchedule ||
|
|
Looping() ||
|
|
mEnvelope ||
|
|
scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) {
|
|
wxASSERT(false);
|
|
scrubbing = false;
|
|
}
|
|
else {
|
|
if (scrubOptions.isPlayingAtSpeed)
|
|
mPlayMode = PLAY_AT_SPEED;
|
|
else if (scrubOptions.isKeyboardScrubbing)
|
|
mPlayMode = PLAY_KEYBOARD_SCRUB;
|
|
else
|
|
mPlayMode = PLAY_SCRUB;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
mWarpedTime = 0.0;
|
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
|
if (Scrubbing())
|
|
mWarpedLength = 0.0f;
|
|
else
|
|
#endif
|
|
mWarpedLength = RealDuration(mT1);
|
|
}
|
|
|
|
double PlaybackSchedule::LimitTrackTime() const
|
|
{
|
|
// Track time readout for the main thread
|
|
// Allows for forward or backward play
|
|
return ClampTrackTime( GetTrackTime() );
|
|
}
|
|
|
|
double PlaybackSchedule::ClampTrackTime( double trackTime ) const
|
|
{
|
|
if (ReversedTime())
|
|
return std::max(mT1, std::min(mT0, trackTime));
|
|
else
|
|
return std::max(mT0, std::min(mT1, trackTime));
|
|
}
|
|
|
|
double PlaybackSchedule::NormalizeTrackTime() const
|
|
{
|
|
// Track time readout for the main thread
|
|
|
|
// dmazzoni: This function is needed for two reasons:
|
|
// One is for looped-play mode - this function makes sure that the
|
|
// position indicator keeps wrapping around. The other reason is
|
|
// more subtle - it's because PortAudio can query the hardware for
|
|
// the current stream time, and this query is not always accurate.
|
|
// Sometimes it's a little behind or ahead, and so this function
|
|
// makes sure that at least we clip it to the selection.
|
|
//
|
|
// msmeyer: There is also the possibility that we are using "cut preview"
|
|
// mode. In this case, we should jump over a defined "gap" in the
|
|
// audio.
|
|
|
|
double absoluteTime;
|
|
|
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
|
// Limit the time between t0 and t1 if not scrubbing.
|
|
// Should the limiting be necessary in any play mode if there are no bugs?
|
|
if (Interactive())
|
|
absoluteTime = GetTrackTime();
|
|
else
|
|
#endif
|
|
absoluteTime = LimitTrackTime();
|
|
|
|
if (mCutPreviewGapLen > 0)
|
|
{
|
|
// msmeyer: We're in cut preview mode, so if we are on the right
|
|
// side of the gap, we jump over it.
|
|
if (absoluteTime > mCutPreviewGapStart)
|
|
absoluteTime += mCutPreviewGapLen;
|
|
}
|
|
|
|
return absoluteTime;
|
|
}
|
|
|
|
bool PlaybackSchedule::PassIsComplete() const
|
|
{
|
|
// Test mTime within the PortAudio callback
|
|
if (Scrubbing())
|
|
return false; // but may be true if playing at speed
|
|
return Overruns( GetTrackTime() );
|
|
}
|
|
|
|
bool PlaybackSchedule::Overruns( double trackTime ) const
|
|
{
|
|
return (ReversedTime() ? trackTime <= mT1 : trackTime >= mT1);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/** @brief Compute the duration (in seconds at playback) of the specified region of the track.
|
|
*
|
|
* Takes a region of the time track (specified by the unwarped time points in the project), and
|
|
* calculates how long it will actually take to play this region back, taking the time track's
|
|
* warping effects into account.
|
|
* @param t0 unwarped time to start calculation from
|
|
* @param t1 unwarped time to stop calculation at
|
|
* @return the warped duration in seconds
|
|
*/
|
|
double ComputeWarpedLength(const Envelope &env, double t0, double t1)
|
|
{
|
|
return env.IntegralOfInverse(t0, t1);
|
|
}
|
|
|
|
/** @brief Compute how much unwarped time must have elapsed if length seconds of warped time has
|
|
* elapsed
|
|
*
|
|
* @param t0 The unwarped time (seconds from project start) at which to start
|
|
* @param length How many seconds of warped time went past.
|
|
* @return The end point (in seconds from project start) as unwarped time
|
|
*/
|
|
double SolveWarpedLength(const Envelope &env, double t0, double length)
|
|
{
|
|
return env.SolveIntegralOfInverse(t0, length);
|
|
}
|
|
}
|
|
|
|
double PlaybackSchedule::AdvancedTrackTime(
|
|
double time, double realElapsed, double speed ) const
|
|
{
|
|
if (ReversedTime())
|
|
realElapsed *= -1.0;
|
|
|
|
// Defense against cases that might cause loops not to terminate
|
|
if ( fabs(mT0 - mT1) < 1e-9 )
|
|
return mT0;
|
|
|
|
if (mEnvelope) {
|
|
wxASSERT( speed == 1.0 );
|
|
|
|
double total=0.0;
|
|
bool foundTotal = false;
|
|
do {
|
|
auto oldTime = time;
|
|
if (foundTotal && fabs(realElapsed) > fabs(total))
|
|
// Avoid SolveWarpedLength
|
|
time = mT1;
|
|
else
|
|
time = SolveWarpedLength(*mEnvelope, time, realElapsed);
|
|
|
|
if (!Looping() || !Overruns( time ))
|
|
break;
|
|
|
|
// Bug1922: The part of the time track outside the loop should not
|
|
// influence the result
|
|
double delta;
|
|
if (foundTotal && oldTime == mT0)
|
|
// Avoid integrating again
|
|
delta = total;
|
|
else {
|
|
delta = ComputeWarpedLength(*mEnvelope, oldTime, mT1);
|
|
if (oldTime == mT0)
|
|
foundTotal = true, total = delta;
|
|
}
|
|
realElapsed -= delta;
|
|
time = mT0;
|
|
} while ( true );
|
|
}
|
|
else {
|
|
time += realElapsed * fabs(speed);
|
|
|
|
// Wrap to start if looping
|
|
if (Looping()) {
|
|
while ( Overruns( time ) ) {
|
|
// 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!
|
|
time -= mT1 - mT0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
void PlaybackSchedule::TrackTimeUpdate(double realElapsed)
|
|
{
|
|
// Update mTime within the PortAudio callback
|
|
|
|
if (Interactive())
|
|
return;
|
|
|
|
auto time = GetTrackTime();
|
|
auto newTime = AdvancedTrackTime( time, realElapsed, 1.0 );
|
|
SetTrackTime( newTime );
|
|
}
|
|
|
|
double PlaybackSchedule::RealDuration(double trackTime1) const
|
|
{
|
|
double duration;
|
|
if (mEnvelope)
|
|
duration = ComputeWarpedLength(*mEnvelope, mT0, trackTime1);
|
|
else
|
|
duration = trackTime1 - mT0;
|
|
return fabs(duration);
|
|
}
|
|
|
|
double PlaybackSchedule::RealTimeRemaining() const
|
|
{
|
|
return mWarpedLength - mWarpedTime;
|
|
}
|
|
|
|
void PlaybackSchedule::RealTimeAdvance( double increment )
|
|
{
|
|
mWarpedTime += increment;
|
|
}
|
|
|
|
void PlaybackSchedule::RealTimeInit( double trackTime )
|
|
{
|
|
if (Scrubbing())
|
|
mWarpedTime = 0.0;
|
|
else
|
|
mWarpedTime = RealDuration( trackTime );
|
|
}
|
|
|
|
void PlaybackSchedule::RealTimeRestart()
|
|
{
|
|
mWarpedTime = 0;
|
|
}
|
|
|
|
double RecordingSchedule::ToConsume() const
|
|
{
|
|
return mDuration - Consumed();
|
|
}
|
|
|
|
double RecordingSchedule::Consumed() const
|
|
{
|
|
return std::max( 0.0, mPosition + TotalCorrection() );
|
|
}
|
|
|
|
double RecordingSchedule::ToDiscard() const
|
|
{
|
|
return std::max(0.0, -( mPosition + TotalCorrection() ) );
|
|
}
|