2019-06-10 18:25:50 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
AudioIOBase.h
|
|
|
|
|
|
|
|
Paul Licameli split from AudioIO.h
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
#ifndef __AUDACITY_AUDIO_IO_BASE__
|
|
|
|
#define __AUDACITY_AUDIO_IO_BASE__
|
|
|
|
|
2019-06-10 19:42:38 +00:00
|
|
|
#include "Audacity.h" // for USE_* macros
|
|
|
|
#include "Experimental.h"
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
#include <cfloat>
|
2019-06-24 16:18:50 +00:00
|
|
|
#include <functional>
|
2019-06-10 19:42:38 +00:00
|
|
|
#include <memory>
|
|
|
|
#include <vector>
|
|
|
|
#include <wx/string.h>
|
|
|
|
#include <wx/weakref.h> // member variable
|
|
|
|
#include "portaudio.h"
|
|
|
|
|
|
|
|
#if USE_PORTMIXER
|
|
|
|
#include "../lib-src/portmixer/include/portmixer.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
class AudioIOBase;
|
|
|
|
|
|
|
|
class AudacityProject;
|
|
|
|
class AudioIOListener;
|
|
|
|
class BoundedEnvelope;
|
2019-06-04 04:20:42 +00:00
|
|
|
class MeterPanelBase;
|
2019-06-10 19:42:38 +00:00
|
|
|
using PRCrossfadeData = std::vector< std::vector < float > >;
|
|
|
|
|
|
|
|
#define BAD_STREAM_TIME (-DBL_MAX)
|
|
|
|
|
|
|
|
// For putting an increment of work in the scrubbing queue
|
|
|
|
struct ScrubbingOptions {
|
|
|
|
ScrubbingOptions() {}
|
|
|
|
|
|
|
|
bool adjustStart {};
|
|
|
|
|
|
|
|
// usually from TrackList::GetEndTime()
|
|
|
|
double maxTime {};
|
|
|
|
double minTime {};
|
|
|
|
|
|
|
|
bool bySpeed {};
|
|
|
|
bool isPlayingAtSpeed{};
|
Bug 1954: Clicks may occur starting/pausing play-at-speed or Scrub
Problem:
On Windows, after 50ms, there is a short period of roughly zero introduced into the output. On Linux, there is also a spike which sounds like a crackle.
In AudioIO::FillBuffers(), Mixer::SetTimesAndSpeed() is called, which sets mT0 and mT1 to a small interval.
In Mixer::MixVariableRates(), all the samples in the interval are used, which means the Resample::Process() is called with last equal to true.
So when Mixer::MixVariableRates() is called again, the resampler is being reused after a call to Process() in which last is true.
It is not stated in the soxr documentation if the resampler will produce valid results in this case, and it's only the scrubbing code which does this.
I think this is the problem, and so the partial fix below avoids this happening.
Partial fix for play-at-speed and keyboard scrubbing:
For these, there is no need to reset the values of mT0 and mT1. (There is no need to allow for the sample position being used to potentially jump around.)
So for these cases, Mixer::SetSpeed() is called, rather than Mixer::SetTimesAndSpeed().
2020-01-15 11:12:40 +00:00
|
|
|
bool isKeyboardScrubbing{};
|
2019-06-10 19:42:38 +00:00
|
|
|
|
|
|
|
double delay {};
|
|
|
|
|
2020-02-11 09:54:45 +00:00
|
|
|
// Initial and limiting values for the speed of a scrub interval:
|
|
|
|
double initSpeed { 1.0 };
|
2019-06-10 19:42:38 +00:00
|
|
|
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.
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{}
|
|
|
|
|
|
|
|
AudacityProject *pProject{};
|
2019-06-04 04:20:42 +00:00
|
|
|
MeterPanelBase *captureMeter{}, *playbackMeter{};
|
2019-06-10 19:42:38 +00:00
|
|
|
BoundedEnvelope *envelope; // for time warping
|
2019-06-24 17:32:08 +00:00
|
|
|
std::shared_ptr< AudioIOListener > listener;
|
2019-06-10 19:42:38 +00:00
|
|
|
double rate;
|
|
|
|
bool playLooped;
|
|
|
|
double cutPreviewGapStart;
|
|
|
|
double cutPreviewGapLen;
|
|
|
|
double * pStartTime;
|
|
|
|
double preRoll;
|
|
|
|
|
|
|
|
#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 {};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// contents may get swapped with empty vector
|
|
|
|
PRCrossfadeData *pCrossfadeData{};
|
2019-06-24 16:18:50 +00:00
|
|
|
|
|
|
|
// 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;
|
2019-06-10 19:42:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
///\brief A singleton object supporting queries of the state of any active
|
|
|
|
/// audio streams, and audio device capabilities
|
2019-06-11 15:06:08 +00:00
|
|
|
class AUDACITY_DLL_API AudioIOBase /* not final */
|
2019-06-10 19:42:38 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
static AudioIOBase *Get();
|
|
|
|
|
2019-06-10 21:36:54 +00:00
|
|
|
virtual ~AudioIOBase();
|
|
|
|
|
2019-06-04 04:20:42 +00:00
|
|
|
void SetCaptureMeter(AudacityProject *project, MeterPanelBase *meter);
|
|
|
|
void SetPlaybackMeter(AudacityProject *project, MeterPanelBase *meter);
|
2019-06-10 19:42:38 +00:00
|
|
|
|
|
|
|
/** \brief update state after changing what audio devices are selected
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
2019-06-11 15:06:40 +00:00
|
|
|
* You can explicitly give the index of the device. If you don't
|
2019-06-10 19:42:38 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static std::vector<long> GetSupportedPlaybackRates(int DevIndex = -1,
|
|
|
|
double rate = 0.0);
|
|
|
|
|
|
|
|
/** \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.
|
|
|
|
*
|
2019-06-11 15:06:40 +00:00
|
|
|
* You can explicitly give the index of the device. If you don't
|
2019-06-10 19:42:38 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static std::vector<long> GetSupportedCaptureRates(int devIndex = -1,
|
|
|
|
double rate = 0.0);
|
|
|
|
|
|
|
|
/** \brief Get a list of sample rates the current input/output device
|
|
|
|
* combination supports.
|
|
|
|
*
|
|
|
|
* 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.
|
2019-06-11 15:06:40 +00:00
|
|
|
* You can explicitly give the indexes of the playDevice/recDevice.
|
2019-06-10 19:42:38 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static std::vector<long> GetSupportedSampleRates(int playDevice = -1,
|
|
|
|
int recDevice = -1,
|
|
|
|
double rate = 0.0);
|
|
|
|
|
|
|
|
/** \brief Get a supported sample rate which can be used a an optimal
|
|
|
|
* default.
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
wxString GetDeviceInfo();
|
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|
|
/** \brief Get diagnostic information on all the available MIDI I/O devices */
|
|
|
|
wxString GetMidiDeviceInfo();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/** \brief Find out if playback / recording is currently paused */
|
|
|
|
bool IsPaused() const;
|
|
|
|
|
2019-06-10 21:36:54 +00:00
|
|
|
virtual void StopStream() = 0;
|
|
|
|
|
2019-06-10 19:42:38 +00:00
|
|
|
/** \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;
|
|
|
|
|
|
|
|
/** \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;
|
|
|
|
|
|
|
|
/** \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;
|
|
|
|
|
|
|
|
/** \brief Returns true if we're monitoring input (but not recording or
|
|
|
|
* playing actual audio) */
|
|
|
|
bool IsMonitoring() 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);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
static std::unique_ptr<AudioIOBase> ugAudioIO;
|
|
|
|
static wxString DeviceName(const PaDeviceInfo* info);
|
|
|
|
static wxString HostName(const PaDeviceInfo* info);
|
|
|
|
|
|
|
|
AudacityProject *mOwningProject;
|
|
|
|
|
|
|
|
/// 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;
|
|
|
|
|
2019-06-04 04:20:42 +00:00
|
|
|
wxWeakRef<MeterPanelBase> mInputMeter{};
|
|
|
|
wxWeakRef<MeterPanelBase> mOutputMeter{};
|
2019-06-10 19:42:38 +00:00
|
|
|
|
|
|
|
#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.
|
Bug 1954: Clicks may occur starting/pausing play-at-speed or Scrub
Problem:
On Windows, after 50ms, there is a short period of roughly zero introduced into the output. On Linux, there is also a spike which sounds like a crackle.
In AudioIO::FillBuffers(), Mixer::SetTimesAndSpeed() is called, which sets mT0 and mT1 to a small interval.
In Mixer::MixVariableRates(), all the samples in the interval are used, which means the Resample::Process() is called with last equal to true.
So when Mixer::MixVariableRates() is called again, the resampler is being reused after a call to Process() in which last is true.
It is not stated in the soxr documentation if the resampler will produce valid results in this case, and it's only the scrubbing code which does this.
I think this is the problem, and so the partial fix below avoids this happening.
Partial fix for play-at-speed and keyboard scrubbing:
For these, there is no need to reset the values of mT0 and mT1. (There is no need to allow for the sample position being used to potentially jump around.)
So for these cases, Mixer::SetSpeed() is called, rather than Mixer::SetTimesAndSpeed().
2020-01-15 11:12:40 +00:00
|
|
|
PLAY_KEYBOARD_SCRUB,
|
2019-06-10 19:42:38 +00:00
|
|
|
#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; }
|
Bug 1954: Clicks may occur starting/pausing play-at-speed or Scrub
Problem:
On Windows, after 50ms, there is a short period of roughly zero introduced into the output. On Linux, there is also a spike which sounds like a crackle.
In AudioIO::FillBuffers(), Mixer::SetTimesAndSpeed() is called, which sets mT0 and mT1 to a small interval.
In Mixer::MixVariableRates(), all the samples in the interval are used, which means the Resample::Process() is called with last equal to true.
So when Mixer::MixVariableRates() is called again, the resampler is being reused after a call to Process() in which last is true.
It is not stated in the soxr documentation if the resampler will produce valid results in this case, and it's only the scrubbing code which does this.
I think this is the problem, and so the partial fix below avoids this happening.
Partial fix for play-at-speed and keyboard scrubbing:
For these, there is no need to reset the values of mT0 and mT1. (There is no need to allow for the sample position being used to potentially jump around.)
So for these cases, Mixer::SetSpeed() is called, rather than Mixer::SetTimesAndSpeed().
2020-01-15 11:12:40 +00:00
|
|
|
bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; }
|
2019-06-10 19:42:38 +00:00
|
|
|
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 a nonnegative real duration to an increment of track time
|
|
|
|
// relative to mT0.
|
|
|
|
double TrackDuration(double realElapsed) 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;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
/** \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;
|
|
|
|
};
|
2019-06-10 18:25:50 +00:00
|
|
|
|
|
|
|
#endif
|