Move RecordingSchedule, PlaybackSchedule to new files
This commit is contained in:
parent
a12ec0d11b
commit
7302fc584f
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
#include "AudioIOBase.h" // to inherit
|
||||
#include "PlaybackSchedule.h" // member variable
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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() ) );
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue