Merge pull request #812 from Paul-Licameli/separate-PlaybackSchedule
Separate playback schedule
This commit is contained in:
commit
cf666c9bfc
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() ) );
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -188,6 +188,8 @@ list( APPEND SOURCES
|
|||
PitchName.h
|
||||
PlatformCompatibility.cpp
|
||||
PlatformCompatibility.h
|
||||
PlaybackSchedule.cpp
|
||||
PlaybackSchedule.h
|
||||
PluginManager.cpp
|
||||
PluginManager.h
|
||||
Prefs.cpp
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() ) );
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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() ||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue