Move RecordingSchedule, PlaybackSchedule to new files

This commit is contained in:
Paul Licameli 2020-12-17 06:23:46 -05:00
parent a12ec0d11b
commit 7302fc584f
6 changed files with 151 additions and 1942 deletions

View File

@ -16,6 +16,7 @@
#include "AudioIOBase.h" // to inherit
#include "PlaybackSchedule.h" // member variable

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,127 +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;
}
std::vector<long> AudioIOBase::GetSupportedPlaybackRates(int devIndex, double rate)
{
if (devIndex == -1)
@ -1181,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>
@ -302,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();
};
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

File diff suppressed because it is too large Load Diff

View File

@ -1,486 +1,167 @@
/**********************************************************************
Audacity: A Digital Audio Editor
PlaybackSchedule.h
Paul Licameli split from AudioIOBase.h
**********************************************************************/
Audacity: A Digital Audio Editor
AudioIOBase.h
Paul Licameli split from AudioIO.h
**********************************************************************/
#ifndef __AUDACITY_AUDIO_IO_BASE__
#define __AUDACITY_AUDIO_IO_BASE__
#ifndef __AUDACITY_PLAYBACK_SCHEDULE__
#define __AUDACITY_PLAYBACK_SCHEDULE__
#include <atomic>
#include <cfloat>
#include <functional>
#include <memory>
#include <vector>
#include <wx/string.h>
#include <wx/weakref.h> // member variable
struct PaDeviceInfo;
typedef void PaStream;
#if USE_PORTMIXER
typedef void PxMixer;
#endif
class AudioIOBase;
class AudacityProject;
class AudioIOListener;
struct AudioIOStartStreamOptions;
class BoundedEnvelope;
// 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)
struct RecordingSchedule {
double mPreRoll{};
double mLatencyCorrection{}; // negative value usually
double mDuration{};
PRCrossfadeData mCrossfadeData;
// For putting an increment of work in the scrubbing queue
struct ScrubbingOptions {
ScrubbingOptions() {}
// These are initialized by the main thread, then updated
// only by the thread calling FillBuffers:
double mPosition{};
bool mLatencyCorrected{};
bool adjustStart {};
// usually from TrackList::GetEndTime()
double maxTime {};
double minTime {};
bool bySpeed {};
bool isPlayingAtSpeed{};
bool isKeyboardScrubbing{};
double delay {};
// Initial and limiting values for the speed of a scrub interval:
double initSpeed { 1.0 };
double minSpeed { 0.0 };
double maxSpeed { 1.0 };
// When maximum speed scrubbing skips to follow the mouse,
// this is the minimum amount of playback allowed at the maximum speed:
double minStutterTime {};
static double MaxAllowedScrubSpeed()
{ return 32.0; } // Is five octaves enough for your amusement?
static double MinAllowedScrubSpeed()
{ return 0.01; } // Mixer needs a lower bound speed. Scrub no slower than this.
double TotalCorrection() const { return mLatencyCorrection - mPreRoll; }
double ToConsume() const;
double Consumed() const;
double ToDiscard() const;
};
// To avoid growing the argument list of StartStream, add fields here
struct AudioIOStartStreamOptions
{
explicit
AudioIOStartStreamOptions(AudacityProject *pProject_, double rate_)
: pProject{ pProject_ }
, envelope(nullptr)
, rate(rate_)
, playLooped(false)
, cutPreviewGapStart(0.0)
, cutPreviewGapLen(0.0)
, pStartTime(NULL)
, preRoll(0.0)
{}
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;
AudacityProject *pProject{};
MeterPanelBase *captureMeter{}, *playbackMeter{};
const BoundedEnvelope *envelope; // for time warping
std::shared_ptr< AudioIOListener > listener;
double rate;
bool playLooped;
double cutPreviewGapStart;
double cutPreviewGapLen;
double * pStartTime;
double preRoll;
/// 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
// Non-null value indicates that scrubbing will happen
// (do not specify a time track, looping, or recording, which
// are all incompatible with scrubbing):
ScrubbingOptions *pScrubbingOptions {};
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
PLAY_KEYBOARD_SCRUB,
#endif
} mPlayMode { PLAY_STRAIGHT };
double mCutPreviewGapStart;
double mCutPreviewGapLen;
// contents may get swapped with empty vector
PRCrossfadeData *pCrossfadeData{};
void Init(
double t0, double t1,
const AudioIOStartStreamOptions &options,
const RecordingSchedule *pRecordingSchedule );
// An unfortunate thing needed just to make scrubbing work on Linux when
// we can't use a separate polling thread.
// The return value is a number of milliseconds to sleep before calling again
std::function< unsigned long() > playbackStreamPrimer;
};
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
///\brief A singleton object supporting queries of the state of any active
/// audio streams, and audio device capabilities
class AUDACITY_DLL_API AudioIOBase /* not final */
{
public:
static AudioIOBase *Get();
virtual ~AudioIOBase();
void SetCaptureMeter(AudacityProject *project, MeterPanelBase *meter);
void SetPlaybackMeter(AudacityProject *project, MeterPanelBase *meter);
/** \brief update state after changing what audio devices are selected
/** \brief Get current track time value, unadjusted
*
* Called when the devices stored in the preferences are changed to update
* the audio mixer capabilities
*
* \todo: Make this do a sample rate query and store the result in the
* AudioIO object to avoid doing it later? Would simplify the
* GetSupported*Rate functions considerably */
void HandleDeviceChange();
/** \brief Get a list of sample rates the output (playback) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitly give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
* Returns a time in seconds.
*/
static std::vector<long> GetSupportedPlaybackRates(int DevIndex = -1,
double rate = 0.0);
double GetTrackTime() const
{ return mTime.load(std::memory_order_relaxed); }
/** \brief Get a list of sample rates the input (recording) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitly give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
/** \brief Set current track time value, unadjusted
*/
static std::vector<long> GetSupportedCaptureRates(int devIndex = -1,
double rate = 0.0);
void SetTrackTime( double time )
{ mTime.store(time, std::memory_order_relaxed); }
/** \brief Get a list of sample rates the current input/output device
* combination supports.
/** \brief Clamps argument to be between mT0 and mT1
*
* Since there is no concept (yet) for different input/output
* sample rates, this currently returns only sample rates that are
* supported on both the output and input device. If no information
* about available sample rates can be fetched, it returns a default
* list.
* You can explicitly give the indexes of the playDevice/recDevice.
* If you don't give them, the selected devices from the preferences
* will be used.
* You may also specify a rate for which to check in addition to the
* standard rates.
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
static std::vector<long> GetSupportedSampleRates(int playDevice = -1,
int recDevice = -1,
double rate = 0.0);
double ClampTrackTime( double trackTime ) const;
/** \brief Get a supported sample rate which can be used a an optimal
* default.
/** \brief Clamps mTime to be between mT0 and mT1
*
* Currently, this uses the first supported rate in the list
* [44100, 48000, highest sample rate]. Used in Project as a default value
* for project rates if one cannot be retrieved from the preferences.
* So all in all not that useful or important really
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
static int GetOptimalSupportedSampleRate();
double LimitTrackTime() const;
/** \brief Array of common audio sample rates
*
* These are the rates we will always support, regardless of hardware support
* for them (by resampling in audacity if needed) */
static const int StandardRates[];
/** \brief How many standard sample rates there are */
static const int NumStandardRates;
/** \brief Get diagnostic information on all the available audio I/O devices
/** \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.
*/
wxString GetDeviceInfo();
double NormalizeTrackTime() const;
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Get diagnostic information on all the available MIDI I/O devices */
wxString GetMidiDeviceInfo();
#endif
void ResetMode() { mPlayMode = PLAY_STRAIGHT; }
/** \brief Find out if playback / recording is currently paused */
bool IsPaused() const;
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(); }
virtual void StopStream() = 0;
// Returns true if a loop pass, or the sole pass of straight play,
// is completed at the current value of mTime
bool PassIsComplete() const;
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording.
*
* When this is false, it's safe to start playing or recording */
bool IsBusy() const;
// Returns true if time equals t1 or is on opposite side of t1, to t0
bool Overruns( double trackTime ) const;
/** \brief Returns true if the audio i/o is running at all, but not during
* cleanup
*
* Doesn't return true if the device has been closed but some disk i/o or
* cleanup is still going on. If you want to know if it's safe to start a
* NEW stream, use IsBusy() */
bool IsStreamActive() const;
bool IsStreamActive(int token) 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;
/** \brief Returns true if the stream is active, or even if audio I/O is
* busy cleaning up its data or writing to disk.
*
* This is used by TrackPanel to determine when a track has been completely
* recorded, and it's safe to flush to disk. */
bool IsAudioTokenActive(int token) 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);
/** \brief Returns true if we're monitoring input (but not recording or
* playing actual audio) */
bool IsMonitoring() const;
// 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;
/* Mixer services are always available. If no stream is running, these
* methods use whatever device is specified by the preferences. If a
* stream *is* running, naturally they manipulate the mixer associated
* with that stream. If no mixer is available, output is emulated and
* input is stuck at 1.0f (a gain is applied to output samples).
*/
void SetMixer(int inputSource);
// How much real time left?
double RealTimeRemaining() const;
protected:
static std::unique_ptr<AudioIOBase> ugAudioIO;
static wxString DeviceName(const PaDeviceInfo* info);
static wxString HostName(const PaDeviceInfo* info);
// Advance the real time position
void RealTimeAdvance( double increment );
AudacityProject *mOwningProject;
// Determine starting duration within the first pass -- sometimes not
// zero
void RealTimeInit( double trackTime );
void RealTimeRestart();
/// True if audio playback is paused
bool mPaused;
/// True when output reaches mT1
bool mMidiOutputComplete{ true };
/// mMidiStreamActive tells when mMidiStream is open for output
bool mMidiStreamActive;
volatile int mStreamToken;
/// Audio playback rate in samples per second
double mRate;
PaStream *mPortStreamV19;
wxWeakRef<MeterPanelBase> mInputMeter{};
wxWeakRef<MeterPanelBase> mOutputMeter{};
#if USE_PORTMIXER
PxMixer *mPortMixer;
float mPreviousHWPlaythrough;
#endif /* USE_PORTMIXER */
bool mEmulateMixerOutputVol;
/** @brief Can we control the hardware input level?
*
* This flag is set to true if using portmixer to control the
* input volume seems to be working (and so we offer the user the control),
* and to false (locking the control out) otherwise. This avoids stupid
* scaled clipping problems when trying to do software emulated input volume
* control */
bool mInputMixerWorks;
float mMixerOutputVol;
// For cacheing supported sample rates
static int mCachedPlaybackIndex;
static std::vector<long> mCachedPlaybackRates;
static int mCachedCaptureIndex;
static std::vector<long> mCachedCaptureRates;
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();
};
/** \brief get the index of the supplied (named) recording device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getRecordDevIndex(const wxString &devName = {});
/** \brief get the index of the device selected in the preferences.
*
* If the device isn't found, returns -1
*/
#if USE_PORTMIXER
static int getRecordSourceIndex(PxMixer *portMixer);
#endif
/** \brief get the index of the supplied (named) playback device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getPlayDevIndex(const wxString &devName = {});
/** \brief Array of audio sample rates to try to use
*
* These are the rates we will check if a device supports, and is as long
* as I can think of (to try and work out what the card can do) */
static const int RatesToTry[];
/** \brief How many sample rates to try */
static const int NumRatesToTry;
};
#endif