Merge pull request #812 from Paul-Licameli/separate-PlaybackSchedule

Separate playback schedule
This commit is contained in:
Paul Licameli 2021-05-14 19:10:29 -04:00 committed by GitHub
commit cf666c9bfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 525 additions and 494 deletions

View File

@ -2587,6 +2587,16 @@ finished:
return retval;
}
double AudioIO::GetStreamTime()
{
// Track time readout for the main thread
if( !IsStreamActive() )
return BAD_STREAM_TIME;
return mPlaybackSchedule.NormalizeTrackTime();
}
//////////////////////////////////////////////////////////////////////
//

View File

@ -16,6 +16,7 @@
#include "AudioIOBase.h" // to inherit
#include "PlaybackSchedule.h" // member variable
@ -576,6 +577,7 @@ protected:
double Consumer( size_t nSamples, double rate );
} mTimeQueue;
PlaybackSchedule mPlaybackSchedule;
};
class AUDACITY_DLL_API AudioIO final
@ -715,6 +717,14 @@ public:
* and playing is true if one or more channels are being played. */
double GetBestRate(bool capturing, bool playing, double sampleRate);
/** \brief During playback, the track time most recently played
*
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
*/
double GetStreamTime();
friend class AudioThread;
#ifdef EXPERIMENTAL_MIDI_OUT
friend class MidiThread;

View File

@ -13,10 +13,10 @@ Paul Licameli split from AudioIO.cpp
#include <wx/log.h>
#include <wx/sstream.h>
#include <wx/txtstrm.h>
#include "Envelope.h"
#include "Prefs.h"
#include "prefs/RecordingPrefs.h"
#include "widgets/MeterPanelBase.h"
@ -379,137 +379,6 @@ bool AudioIOBase::IsMonitoring() const
return ( mPortStreamV19 && mStreamToken==0 );
}
void AudioIOBase::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 AudioIOBase::PlaybackSchedule::LimitTrackTime() const
{
// Track time readout for the main thread
// Allows for forward or backward play
return ClampTrackTime( GetTrackTime() );
}
double AudioIOBase::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 AudioIOBase::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;
}
double AudioIOBase::GetStreamTime()
{
// Track time readout for the main thread
if( !IsStreamActive() )
return BAD_STREAM_TIME;
return mPlaybackSchedule.NormalizeTrackTime();
}
std::vector<long> AudioIOBase::GetSupportedPlaybackRates(int devIndex, double rate)
{
if (devIndex == -1)
@ -1191,163 +1060,3 @@ wxString AudioIOBase::GetMidiDeviceInfo()
return o.GetString();
}
#endif
bool AudioIOBase::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 AudioIOBase::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 AudioIOBase::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 AudioIOBase::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 AudioIOBase::PlaybackSchedule::RealDuration(double trackTime1) const
{
double duration;
if (mEnvelope)
duration = ComputeWarpedLength(*mEnvelope, mT0, trackTime1);
else
duration = trackTime1 - mT0;
return fabs(duration);
}
double AudioIOBase::PlaybackSchedule::RealTimeRemaining() const
{
return mWarpedLength - mWarpedTime;
}
void AudioIOBase::PlaybackSchedule::RealTimeAdvance( double increment )
{
mWarpedTime += increment;
}
void AudioIOBase::PlaybackSchedule::RealTimeInit( double trackTime )
{
if (Scrubbing())
mWarpedTime = 0.0;
else
mWarpedTime = RealDuration( trackTime );
}
void AudioIOBase::PlaybackSchedule::RealTimeRestart()
{
mWarpedTime = 0;
}
double AudioIOBase::RecordingSchedule::ToConsume() const
{
return mDuration - Consumed();
}
double AudioIOBase::RecordingSchedule::Consumed() const
{
return std::max( 0.0, mPosition + TotalCorrection() );
}
double AudioIOBase::RecordingSchedule::ToDiscard() const
{
return std::max(0.0, -( mPosition + TotalCorrection() ) );
}

View File

@ -14,7 +14,6 @@ Paul Licameli split from AudioIO.h
#include <atomic>
#include <cfloat>
#include <functional>
#include <memory>
@ -34,7 +33,9 @@ class AudioIOBase;
class AudacityProject;
class AudioIOListener;
class BoundedEnvelope;
class MeterPanelBase;
// Windows build needs complete type for parameter of wxWeakRef
// class MeterPanelBase;
#include "widgets/MeterPanelBase.h"
using PRCrossfadeData = std::vector< std::vector < float > >;
#define BAD_STREAM_TIME (-DBL_MAX)
@ -193,14 +194,6 @@ public:
*/
static int GetOptimalSupportedSampleRate();
/** \brief During playback, the track time most recently played
*
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
*/
double GetStreamTime();
/** \brief Array of common audio sample rates
*
* These are the rates we will always support, regardless of hardware support
@ -308,152 +301,7 @@ protected:
static std::vector<long> mCachedSampleRates;
static double mCachedBestRateIn;
struct RecordingSchedule {
double mPreRoll{};
double mLatencyCorrection{}; // negative value usually
double mDuration{};
PRCrossfadeData mCrossfadeData;
// These are initialized by the main thread, then updated
// only by the thread calling FillBuffers:
double mPosition{};
bool mLatencyCorrected{};
double TotalCorrection() const { return mLatencyCorrection - mPreRoll; }
double ToConsume() const;
double Consumed() const;
double ToDiscard() const;
};
struct PlaybackSchedule {
/// Playback starts at offset of mT0, which is measured in seconds.
double mT0;
/// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
double mT1;
/// Current track time position during playback, in seconds.
/// Initialized by the main thread but updated by worker threads during
/// playback or recording, and periodically reread by the main thread for
/// purposes such as display update.
std::atomic<double> mTime;
/// Accumulated real time (not track position), starting at zero (unlike
/// mTime), and wrapping back to zero each time around looping play.
/// Thus, it is the length in real seconds between mT0 and mTime.
double mWarpedTime;
/// Real length to be played (if looping, for each pass) after warping via a
/// time track, computed just once when starting the stream.
/// Length in real seconds between mT0 and mT1. Always positive.
double mWarpedLength;
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
// else they are used in updating mTime,
// and when not scrubbing or playing looped, mTime is also used
// in the test for termination of playback.
// 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
const BoundedEnvelope *mEnvelope;
volatile enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
PLAY_KEYBOARD_SCRUB,
#endif
} mPlayMode { PLAY_STRAIGHT };
double mCutPreviewGapStart;
double mCutPreviewGapLen;
void Init(
double t0, double t1,
const AudioIOStartStreamOptions &options,
const RecordingSchedule *pRecordingSchedule );
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
/** \brief Get current track time value, unadjusted
*
* Returns a time in seconds.
*/
double GetTrackTime() const
{ return mTime.load(std::memory_order_relaxed); }
/** \brief Set current track time value, unadjusted
*/
void SetTrackTime( double time )
{ mTime.store(time, std::memory_order_relaxed); }
/** \brief Clamps argument to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double ClampTrackTime( double trackTime ) const;
/** \brief Clamps mTime to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double LimitTrackTime() const;
/** \brief Normalizes mTime, clamping it and handling gaps from cut preview.
*
* Clamps the time (unless scrubbing), and skips over the cut section.
* Returns a time in seconds.
*/
double NormalizeTrackTime() const;
void ResetMode() { mPlayMode = PLAY_STRAIGHT; }
bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; }
bool Looping() const { return mPlayMode == PLAY_LOOPED; }
bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; }
bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; }
bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); }
// Returns true if a loop pass, or the sole pass of straight play,
// is completed at the current value of mTime
bool PassIsComplete() const;
// Returns true if time equals t1 or is on opposite side of t1, to t0
bool Overruns( double trackTime ) const;
// Compute the NEW track time for the given one and a real duration,
// taking into account whether the schedule is for looping
double AdvancedTrackTime(
double trackTime, double realElapsed, double speed) const;
// Use the function above in the callback after consuming samples from the
// playback ring buffers, during usual straight or looping play
void TrackTimeUpdate(double realElapsed);
// Convert time between mT0 and argument to real duration, according to
// time track if one is given; result is always nonnegative
double RealDuration(double trackTime1) const;
// How much real time left?
double RealTimeRemaining() const;
// Advance the real time position
void RealTimeAdvance( double increment );
// Determine starting duration within the first pass -- sometimes not
// zero
void RealTimeInit( double trackTime );
void RealTimeRestart();
} mPlaybackSchedule;
protected:
/** \brief get the index of the supplied (named) recording device, or the
* device selected in the preferences if none given.
*

View File

@ -188,6 +188,8 @@ list( APPEND SOURCES
PitchName.h
PlatformCompatibility.cpp
PlatformCompatibility.h
PlaybackSchedule.cpp
PlaybackSchedule.h
PluginManager.cpp
PluginManager.h
Prefs.cpp

View File

@ -11,7 +11,7 @@
#include "LyricsWindow.h"
#include "Lyrics.h"
#include "AudioIOBase.h"
#include "AudioIO.h"
#include "CommonCommandFlags.h"
#include "prefs/GUISettings.h" // for RTL_WORKAROUND
#include "Project.h"
@ -160,7 +160,7 @@ void LyricsWindow::OnTimer(wxCommandEvent &event)
{
if (ProjectAudioIO::Get( *mProject ).IsAudioActive())
{
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
GetLyricsPanel()->Update(gAudioIO->GetStreamTime());
}
else

View File

@ -1353,7 +1353,7 @@ void MixerBoard::OnTimer(wxCommandEvent &event)
// audacityAudioCallback where it calls gAudioIO->mOutputMeter->UpdateDisplay().
if (ProjectAudioIO::Get( *mProject ).IsAudioActive())
{
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
UpdateMeters(
gAudioIO->GetStreamTime(),
(ProjectAudioManager::Get( *mProject ).GetLastPlayMode()

295
src/PlaybackSchedule.cpp Normal file
View File

@ -0,0 +1,295 @@
/**********************************************************************
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() ) );
}

167
src/PlaybackSchedule.h Normal file
View File

@ -0,0 +1,167 @@
/**********************************************************************
Audacity: A Digital Audio Editor
PlaybackSchedule.h
Paul Licameli split from AudioIOBase.h
**********************************************************************/
#ifndef __AUDACITY_PLAYBACK_SCHEDULE__
#define __AUDACITY_PLAYBACK_SCHEDULE__
#include <atomic>
#include <vector>
struct AudioIOStartStreamOptions;
class BoundedEnvelope;
using PRCrossfadeData = std::vector< std::vector < float > >;
struct RecordingSchedule {
double mPreRoll{};
double mLatencyCorrection{}; // negative value usually
double mDuration{};
PRCrossfadeData mCrossfadeData;
// These are initialized by the main thread, then updated
// only by the thread calling FillBuffers:
double mPosition{};
bool mLatencyCorrected{};
double TotalCorrection() const { return mLatencyCorrection - mPreRoll; }
double ToConsume() const;
double Consumed() const;
double ToDiscard() const;
};
struct AUDACITY_DLL_API PlaybackSchedule {
/// Playback starts at offset of mT0, which is measured in seconds.
double mT0;
/// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
double mT1;
/// Current track time position during playback, in seconds.
/// Initialized by the main thread but updated by worker threads during
/// playback or recording, and periodically reread by the main thread for
/// purposes such as display update.
std::atomic<double> mTime;
/// Accumulated real time (not track position), starting at zero (unlike
/// mTime), and wrapping back to zero each time around looping play.
/// Thus, it is the length in real seconds between mT0 and mTime.
double mWarpedTime;
/// Real length to be played (if looping, for each pass) after warping via a
/// time track, computed just once when starting the stream.
/// Length in real seconds between mT0 and mT1. Always positive.
double mWarpedLength;
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
// else they are used in updating mTime,
// and when not scrubbing or playing looped, mTime is also used
// in the test for termination of playback.
// 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
const BoundedEnvelope *mEnvelope;
volatile enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
PLAY_KEYBOARD_SCRUB,
#endif
} mPlayMode { PLAY_STRAIGHT };
double mCutPreviewGapStart;
double mCutPreviewGapLen;
void Init(
double t0, double t1,
const AudioIOStartStreamOptions &options,
const RecordingSchedule *pRecordingSchedule );
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
/** \brief Get current track time value, unadjusted
*
* Returns a time in seconds.
*/
double GetTrackTime() const
{ return mTime.load(std::memory_order_relaxed); }
/** \brief Set current track time value, unadjusted
*/
void SetTrackTime( double time )
{ mTime.store(time, std::memory_order_relaxed); }
/** \brief Clamps argument to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double ClampTrackTime( double trackTime ) const;
/** \brief Clamps mTime to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double LimitTrackTime() const;
/** \brief Normalizes mTime, clamping it and handling gaps from cut preview.
*
* Clamps the time (unless scrubbing), and skips over the cut section.
* Returns a time in seconds.
*/
double NormalizeTrackTime() const;
void ResetMode() { mPlayMode = PLAY_STRAIGHT; }
bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; }
bool Looping() const { return mPlayMode == PLAY_LOOPED; }
bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; }
bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; }
bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); }
// Returns true if a loop pass, or the sole pass of straight play,
// is completed at the current value of mTime
bool PassIsComplete() const;
// Returns true if time equals t1 or is on opposite side of t1, to t0
bool Overruns( double trackTime ) const;
// Compute the NEW track time for the given one and a real duration,
// taking into account whether the schedule is for looping
double AdvancedTrackTime(
double trackTime, double realElapsed, double speed) const;
// Use the function above in the callback after consuming samples from the
// playback ring buffers, during usual straight or looping play
void TrackTimeUpdate(double realElapsed);
// Convert time between mT0 and argument to real duration, according to
// time track if one is given; result is always nonnegative
double RealDuration(double trackTime1) const;
// How much real time left?
double RealTimeRemaining() const;
// Advance the real time position
void RealTimeAdvance( double increment );
// Determine starting duration within the first pass -- sometimes not
// zero
void RealTimeInit( double trackTime );
void RealTimeRestart();
};
#endif

View File

@ -15,7 +15,10 @@ Paul Licameli split from AudacityProject.h
#include <wx/weakref.h>
class AudacityProject;
class MeterPanelBase;
// Windows build needs complete type for parameter of wxWeakRef
// class MeterPanelBase;
#include "widgets/MeterPanelBase.h"
///\ brief Holds per-project state needed for interaction with AudioIO,
/// including the audio stream token and pointers to meters

View File

@ -1075,7 +1075,7 @@ bool ProjectAudioManager::DoPlayStopSelect( bool click, bool shift )
auto token = ProjectAudioIO::Get( project ).GetAudioIOToken();
auto &viewInfo = ViewInfo::Get( project );
auto &selection = viewInfo.selectedRegion;
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
//If busy, stop playing, make sure everything is unpaused.
if (scrubber.HasMark() ||

View File

@ -13,6 +13,7 @@ Paul Licameli split from AudacityProject.cpp
#include "AllThemeResources.h"
#include "AudioIO.h"
#include "Menus.h"
#include "Project.h"
#include "ProjectAudioIO.h"
@ -1660,7 +1661,7 @@ void ProjectWindow::TP_HandleResize()
ProjectWindow::PlaybackScroller::PlaybackScroller(AudacityProject *project)
: mProject(project)
{
ViewInfo::Get( *mProject ).Bind(EVT_TRACK_PANEL_TIMER,
mProject->Bind(EVT_TRACK_PANEL_TIMER,
&PlaybackScroller::OnTimer,
this);
}
@ -1670,9 +1671,12 @@ void ProjectWindow::PlaybackScroller::OnTimer(wxCommandEvent &event)
// Let other listeners get the notification
event.Skip();
auto gAudioIO = AudioIO::Get();
mRecentStreamTime = gAudioIO->GetStreamTime();
auto cleanup = finally([&]{
// Propagate the message to other listeners bound to this
this->ProcessEvent( event );
this->SafelyProcessEvent( event );
});
if(!ProjectAudioIO::Get( *mProject ).IsAudioActive())
@ -1692,7 +1696,7 @@ void ProjectWindow::PlaybackScroller::OnTimer(wxCommandEvent &event)
auto &viewInfo = ViewInfo::Get( *mProject );
auto &trackPanel = GetProjectPanel( *mProject );
const int posX = viewInfo.TimeToPosition(viewInfo.mRecentStreamTime);
const int posX = viewInfo.TimeToPosition(mRecentStreamTime);
auto width = viewInfo.GetTracksUsableWidth();
int deltaX;
switch (mMode)
@ -1721,7 +1725,7 @@ void ProjectWindow::ZoomInByFactor( double ZoomFactor )
auto &project = mProject;
auto &viewInfo = ViewInfo::Get( project );
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
// LLL: Handling positioning differently when audio is
// actively playing. Don't do this if paused.
if (gAudioIO->IsStreamActive(

View File

@ -75,11 +75,17 @@ public:
mMode = mode;
}
double GetRecentStreamTime() const { return mRecentStreamTime; }
private:
void OnTimer(wxCommandEvent &event);
AudacityProject *mProject;
Mode mMode { Mode::Off };
// During timer update, grab the volatile stream time just once, so that
// various other drawing code can use the exact same value.
double mRecentStreamTime{ -1.0 };
};
PlaybackScroller &GetPlaybackScroller() { return *mPlaybackScroller; }

View File

@ -14,7 +14,6 @@ Paul Licameli
#include <algorithm>
#include "AudioIOBase.h"
#include "Prefs.h"
#include "Project.h"
#include "xml/XMLWriter.h"
@ -150,12 +149,7 @@ void NotifyingSelectedRegion::Notify( bool delayed )
static const AudacityProject::AttachedObjects::RegisteredFactory key{
[]( AudacityProject &project ) {
auto result =
std::make_unique<ViewInfo>(0.0, 1.0, ZoomInfo::GetDefaultZoom());
project.Bind(EVT_TRACK_PANEL_TIMER,
&ViewInfo::OnTimer,
result.get());
return std::move( result );
return std::make_unique<ViewInfo>(0.0, 1.0, ZoomInfo::GetDefaultZoom());
}
};
@ -180,7 +174,6 @@ ViewInfo::ViewInfo(double start, double screenDuration, double pixelsPerSecond)
, scrollStep(16)
, bUpdateTrackIndicator(true)
, bScrollBeyondZero(false)
, mRecentStreamTime(-1.0)
{
UpdatePrefs();
}
@ -248,15 +241,6 @@ bool ViewInfo::ReadXMLAttribute(const wxChar *attr, const wxChar *value)
return false;
}
void ViewInfo::OnTimer(wxCommandEvent &event)
{
auto gAudioIO = AudioIOBase::Get();
mRecentStreamTime = gAudioIO->GetStreamTime();
event.Skip();
// Propagate the message to other listeners bound to this
this->ProcessEvent( event );
}
int ViewInfo::UpdateScrollPrefsID()
{
static int value = wxNewId();

View File

@ -224,16 +224,9 @@ public:
bool bScrollBeyondZero;
bool bAdjustSelectionEdges;
// During timer update, grab the volatile stream time just once, so that
// various other drawing code can use the exact same value.
double mRecentStreamTime;
void WriteXMLAttributes(XMLWriter &xmlFile) const;
bool ReadXMLAttribute(const wxChar *attr, const wxChar *value);
// Receive track panel timer notifications
void OnTimer(wxCommandEvent &event);
private:
int mHeight{ 0 };
};

View File

@ -1,4 +1,4 @@
#include "../AudioIOBase.h"
#include "../AudioIO.h"
#include "../Clipboard.h"
#include "../CommonCommandFlags.h"
#include "../LabelTrack.h"
@ -302,7 +302,7 @@ void OnAddLabelPlaying(const CommandContext &context)
auto &project = context.project;
auto token = ProjectAudioIO::Get( project ).GetAudioIOToken();
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
if (token > 0 &&
gAudioIO->IsStreamActive(token)) {
double indicator = gAudioIO->GetStreamTime();

View File

@ -504,7 +504,7 @@ void OnSetLeftSelection(const CommandContext &context)
auto &window = GetProjectFrame( project );
bool bSelChanged = false;
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
if ((token > 0) && gAudioIO->IsStreamActive(token))
{
double indicator = gAudioIO->GetStreamTime();
@ -543,7 +543,7 @@ void OnSetRightSelection(const CommandContext &context)
auto &window = GetProjectFrame( project );
bool bSelChanged = false;
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
if ((token > 0) && gAudioIO->IsStreamActive(token))
{
double indicator = gAudioIO->GetStreamTime();

View File

@ -51,7 +51,7 @@ with changes in the SelectionBar.
#include <wx/statline.h>
#include "../AudioIOBase.h"
#include "../AudioIO.h"
#include "../AColor.h"
#include "../KeyboardCapture.h"
#include "../Prefs.h"
@ -599,7 +599,7 @@ void SelectionBar::OnIdle( wxIdleEvent &evt )
auto &projectAudioIO = ProjectAudioIO::Get( project );
if ( projectAudioIO.IsAudioActive() ){
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
audioTime = gAudioIO->GetStreamTime();
}
else {

View File

@ -362,7 +362,7 @@ void TimeToolBar::OnIdle(wxIdleEvent &evt)
auto &projectAudioIO = ProjectAudioIO::Get(mProject);
if (projectAudioIO.IsAudioActive()) {
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
audioTime = gAudioIO->GetStreamTime();
}
else {

View File

@ -174,13 +174,13 @@ void PlayIndicatorOverlay::OnTimer(wxCommandEvent &event)
}
}
else {
// Calculate the horizontal position of the indicator
const double playPos = viewInfo.mRecentStreamTime;
auto &window = ProjectWindow::Get( *mProject );
auto &scroller = window.GetPlaybackScroller();
// Calculate the horizontal position of the indicator
const double playPos = scroller.GetRecentStreamTime();
using Mode = ProjectWindow::PlaybackScroller::Mode;
const Mode mode =
window.GetPlaybackScroller().GetMode();
const Mode mode = scroller.GetMode();
const bool pinned = ( mode == Mode::Pinned || mode == Mode::Right );
// Use a small tolerance to avoid flicker of play head pinned all the way

View File

@ -1139,7 +1139,7 @@ void Scrubber::DoKeyboardScrub(bool backwards, bool keyUp)
if (keyUp) {
auto &scrubber = Scrubber::Get(project);
if (scrubber.IsKeyboardScrubbing() && scrubber.IsBackwards() == backwards) {
auto gAudioIO = AudioIOBase::Get();
auto gAudioIO = AudioIO::Get();
auto time = gAudioIO->GetStreamTime();
auto &viewInfo = ViewInfo::Get(project);
auto &selection = viewInfo.selectedRegion;